ハミルトニアンシミュレーション回路のコンパイル手法#
Hamlib のハミルトニアンシミュレーション回路を用いて、SABRE、AI 搭載トランスパイラー、Rustiq のコンパイル手法を比較します。
使用量の目安: IBM Heron プロセッサで 1 分未満(注意: これはあくまで目安です。実際の実行時間は異なる場合があります。)
{/* cspell:ignore Rustiq, nshuffles, edgecolors, edgecolor, Hamlib, Benchpress, Brugiere, Goubault, Martiel, Dubal, Lishman, Ivrii, fontweight, fontsize, textprops, wedgeprops, startangle, autopct, Hellinger, iloc, ylabel, frameon, ylims, CMHSC */}
学習成果#
このチュートリアルを終えると、次の内容が理解できるようになります。
レイアウトとルーティングの最適化のために、SABRE を用いた Qiskit トランスパイラーを使用する方法
高度な回路最適化のために、AI 搭載トランスパイラーを活用する方法
ハミルトニアンシミュレーション回路に含まれる
PauliEvolutionGate演算を合成するために、Rustiq プラグインを使用する方法2 量子ビット深さ、総ゲート数、実行時間を用いて、コンパイル手法をベンチマークし比較する方法
前提知識#
このチュートリアルに進む前に、次のトピックについて理解しておくことをお勧めします。
背景#
量子回路のコンパイルは、高レベルの量子アルゴリズムを、対象ハードウェアの制約を満たす物理回路へと変換します。効果的なコンパイルは回路の深さとゲート数を大幅に削減でき、そのいずれも近未来の量子デバイスにおける結果の品質に直接影響します。
このチュートリアルでは、PauliEvolutionGate で構成したハミルトニアンシミュレーション回路に対して、3 つのコンパイル手法をベンチマークします。これらの回路は量子ビット間の 2 体相互作用(\(ZZ\)、\(XX\)、\(YY\) 項など)をモデル化するもので、量子化学、物性物理、材料科学において一般的に用いられます。
ベンチマーク回路は Hamlib コレクションのもので、Benchpress リポジトリ経由で取得します。Hamlib は代表的なハミルトニアンの標準的なセットを提供しており、現実的なシミュレーションのワークロードでコンパイル戦略を比較できるようになっています。
コンパイル手法の概要#
SABRE を用いた Qiskit トランスパイラー#
Qiskit トランスパイラーは、回路のレイアウトとルーティングを最適化するために SABRE(SWAP-based BidiREctional heuristic search)アルゴリズムを使用します。SABRE は、ハードウェアの接続性の制約を満たしつつ、SWAP ゲートとそれが回路の深さに与える影響を最小化することに重点を置いています。汎用的な手法であり、性能とコンパイル時間のバランスが良好です。詳しくは [1] を参照してください。SABRE の利点とパラメーターの探索については、別の チュートリアル で詳しく扱っています。
AI 搭載トランスパイラー#
AI 搭載トランスパイラーは、回路構造とハードウェア制約のパターンを解析することで、機械学習を用いて最適なトランスパイル戦略を予測します。また、強化学習に基づく合成アプローチでパウリネットワーク回路を対象とする AIPauliNetworkSynthesis パスを適用することもできます。詳しくは [2] および [3] を参照してください。
Rustiq プラグイン#
Rustiq プラグインは、トロッター化されたダイナミクスで一般的に用いられるパウリ回転を表す PauliEvolutionGate 演算に特化した、高度な合成手法を提供します。ハミルトニアンシミュレーションのワークロードに対して、低深さの回路分解を生成するように設計されています。詳しくは [4] を参照してください。
主要な指標#
3 つの手法を次の指標で比較します。
2 量子ビット深さ: 2 量子ビットゲートのみを数えた回路の深さ。実機での忠実度のボトルネックとなることが多い指標です。
回路サイズ(総ゲート数): トランスパイル後の回路に含まれるゲートの総数。
実行時間: トランスパイルに要した実時間。
要件#
このチュートリアルを始める前に、次のものがインストールされていることを確認してください。
Qiskit SDK v2.0 以降(可視化 サポート付き)
Qiskit Runtime v0.22 以降(
pip install qiskit-ibm-runtime)Qiskit Aer(
pip install qiskit-aer)Qiskit IBM Transpiler(
pip install qiskit-ibm-transpiler)Qiskit AI Transpiler ローカルモード(
pip install qiskit_ibm_ai_local_transpiler)Networkx(
pip install networkx)
セットアップ#
from qiskit.circuit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
from qiskit.circuit.library import PauliEvolutionGate
from qiskit_ibm_transpiler import generate_ai_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel, depolarizing_error
from collections import Counter
from statistics import mean, stdev
from scipy.sparse import SparseEfficiencyWarning
import time
import warnings
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
import json
import requests
import logging
# 冗長なロガーと警告を抑制する
logging.getLogger(
"qiskit_ibm_transpiler.wrappers.ai_local_synthesis"
).setLevel(logging.ERROR)
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category=SparseEfficiencyWarning)
seed = 42 # 再現性のためのシード
バックエンドへの接続#
小規模な例と大規模な例の両方で使用するバックエンドを選択します。バックエンドによって、トランスパイラーが対象とするカップリングマップと基底ゲートが決まります。
# QiskitRuntimeService.save_account(channel="ibm_quantum_platform",
# token="<YOUR-API-KEY>", overwrite=True, set_as_default=True)
service = QiskitRuntimeService(channel="ibm_quantum_platform")
backend = service.least_busy(operational=True, simulator=False)
print(f"使用するバックエンド: {backend.name}")
Using backend: ibm_pittsburgh
パスマネージャーの定義#
3 つのコンパイル手法を設定します。
# SABRE のパスマネージャー(最適化レベル 3 における Qiskit のデフォルト)
pm_sabre = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
# AI トランスパイラーのパスマネージャー(ローカルモード)
pm_ai = generate_ai_pass_manager(
backend=backend, optimization_level=3, ai_optimization_level=3
)
# PauliEvolutionGate の合成のための Rustiq のパスマネージャー
hls_config = HLSConfig(
PauliEvolution=[
(
"rustiq",
{
"nshuffles": 400,
"upto_phase": True,
"fix_clifford": True,
"preserve_order": False,
"metric": "depth",
},
)
]
)
pm_rustiq = generate_preset_pass_manager(
optimization_level=3,
backend=backend,
hls_config=hls_config,
seed_transpiler=seed,
)
ヘルパー関数の定義#
次の関数は、指定したパスマネージャーで回路のリストをトランスパイルし、各回路について主要な指標(2 量子ビット深さ、回路サイズ、実行時間)を記録します。
def capture_transpilation_metrics(
results, pass_manager, circuits, method_name
):
"""
回路をトランスパイルし、回路ごとに 1 件の指標レコードを ``results``
に追加する。
Args:
results (list): 指標レコードを追加する辞書のリスト。
pass_manager: トランスパイルに使用するパスマネージャー。
circuits (list): トランスパイルする量子回路のリスト。
method_name (str): トランスパイル手法の名前。
Returns:
list: トランスパイル後の回路のリスト。
"""
transpiled_circuits = []
for i, qc in enumerate(circuits):
start_time = time.time()
transpiled_qc = pass_manager.run(qc)
end_time = time.time()
# 手法間で条件をそろえるために swap を分解する
transpiled_qc = transpiled_qc.decompose(gates_to_decompose=["swap"])
transpilation_time = end_time - start_time
two_qubit_depth = transpiled_qc.depth(
lambda x: x.operation.num_qubits == 2
)
circuit_size = transpiled_qc.size()
results.append(
{
"method": method_name,
"qc_name": qc.name,
"qc_index": i,
"num_qubits": qc.num_qubits,
"two_qubit_depth": two_qubit_depth,
"size": circuit_size,
"runtime": transpilation_time,
}
)
transpiled_circuits.append(transpiled_qc)
print(
f"[{method_name}] 回路 {i} ({qc.name}): "
f"2Q 深さ={two_qubit_depth}, サイズ={circuit_size}, "
f"時間={transpilation_time:.2f}s"
)
return transpiled_circuits
def _method_order(results):
"""重複を除いた手法名を、最初に現れた順で返す。"""
order = []
for r in results:
if r["method"] not in order:
order.append(r["method"])
return order
def print_summary_table(results):
"""
コンパイル手法ごとに各指標の平均と標準偏差を表示し、続いて SABRE に
対する平均改善率を表示する。
"""
metrics = [
("two_qubit_depth", "2Q 深さ"),
("size", "ゲート数"),
("runtime", "実行時間 (s)"),
]
methods = _method_order(results)
by_method = {m: [r for r in results if r["method"] == m] for m in methods}
sabre_by_index = {r["qc_index"]: r for r in by_method.get("SABRE", [])}
col_w = 22
name_w = max(len(m) for m in methods)
header = f"{'手法':<{name_w}}" + "".join(
f" {label:>{col_w}}" for _, label in metrics
)
print("コンパイル手法ごとの平均 +/- 標準偏差")
print(header)
print("-" * len(header))
for method in methods:
cells = []
for key, _ in metrics:
values = [r[key] for r in by_method[method]]
std = stdev(values) if len(values) > 1 else 0.0
cells.append(f"{mean(values):,.1f} +/- {std:,.1f}")
print(
f"{method:<{name_w}}" + "".join(f" {c:>{col_w}}" for c in cells)
)
others = [m for m in methods if m != "SABRE"]
if others and sabre_by_index:
print()
print("SABRE に対する平均改善率 %(正の値 = SABRE より良い)")
print(header)
print("-" * len(header))
for method in others:
cells = []
for key, _ in metrics:
pct = [
(sabre_by_index[r["qc_index"]][key] - r[key])
/ sabre_by_index[r["qc_index"]][key]
* 100
for r in by_method[method]
if sabre_by_index.get(r["qc_index"])
and sabre_by_index[r["qc_index"]][key]
]
if pct:
std = stdev(pct) if len(pct) > 1 else 0.0
cells.append(f"{mean(pct):+.1f}% +/- {std:.1f}%")
else:
cells.append("n/a")
print(
f"{method:<{name_w}}"
+ "".join(f" {c:>{col_w}}" for c in cells)
)
def print_per_circuit_comparison(results, num_rows=5):
"""
先頭 ``num_rows`` 個の回路(量子ビット数でソート)について、指標ごとに
コンパイル手法を比較して表示する。各指標の最良(最小)の値には
アスタリスクを付ける。
"""
metrics = [
("two_qubit_depth", "2Q 深さ"),
("size", "ゲート数"),
("runtime", "実行時間 (s)"),
]
methods = _method_order(results)
by_index = {}
for r in results:
by_index.setdefault(r["qc_index"], {})[r["method"]] = r
ordered = sorted(
by_index.items(),
key=lambda kv: (next(iter(kv[1].values()))["num_qubits"], kv[0]),
)[:num_rows]
for key, label in metrics:
print(f"{label}(量子ビット数順で先頭 {num_rows} 回路); * = 最良")
header = f"{'Idx':>3} {'回路':<16} {'Q':>3}" + "".join(
f"{m:>9}" for m in methods
)
print(header)
print("-" * len(header))
for idx, method_map in ordered:
any_record = next(iter(method_map.values()))
present = {
m: method_map[m][key] for m in methods if m in method_map
}
best = min(present.values())
line = (
f"{idx:>3} {any_record['qc_name'][:16]:<16} "
f"{any_record['num_qubits']:>3}"
)
for m in methods:
value = method_map[m][key]
text = f"{value:.2f}" if key == "runtime" else f"{int(value)}"
if value == best:
text += "*"
line += f"{text:>9}"
print(line)
print()
Hamlib からハミルトニアン回路を読み込む#
Benchpress リポジトリから代表的なハミルトニアンのセットを読み込み、PauliEvolutionGate 回路を構成します。バックエンドの量子ビット数を超える回路は除外し、さらに(トランスパイル時間を妥当な範囲に保つため)分解後のサイズが 1,500 ゲートを超える回路も除外します。
# benchpress リポジトリからハミルトニアンの JSON を取得する
url = (
"https://raw.githubusercontent.com/Qiskit/benchpress/"
"e7b29ef7be4cc0d70237b8fdc03edbd698908eff/"
"benchpress/hamiltonian/hamlib/100_representative.json"
)
response = requests.get(url)
response.raise_for_status()
ham_records = json.loads(response.text)
# バックエンドに対して大きすぎる回路を除外する
ham_records = [
h for h in ham_records if h["ham_qubits"] <= backend.num_qubits
]
# PauliEvolutionGate 回路を構成する
qc_ham_list = []
for h in ham_records:
terms = h["ham_hamlib_hamiltonian_terms"]
coeff = h["ham_hamlib_hamiltonian_coefficients"]
num_qubits = h["ham_qubits"]
name = h["ham_problem"]
evo_gate = PauliEvolutionGate(SparsePauliOp(terms, coeff))
qc = QuantumCircuit(num_qubits)
qc.name = name
qc.append(evo_gate, range(num_qubits))
qc_ham_list.append(qc)
# トランスパイルが妥当な時間で完了するように、分解後のサイズが
# 1500 ゲートを超える回路を除外する
qc_ham_list = [qc for qc in qc_ham_list if qc.decompose().size() <= 1500]
print(f"読み込んだハミルトニアン回路の総数: {len(qc_ham_list)}")
min_qubits = min(qc.num_qubits for qc in qc_ham_list)
max_qubits = max(qc.num_qubits for qc in qc_ham_list)
print(f"量子ビット数の範囲: {min_qubits} 〜 {max_qubits}")
Total Hamiltonian circuits loaded: 42
Qubit range: 2 to 112
回路を小規模(20 量子ビット未満)と大規模(20 量子ビット以上)のグループに分けます。
qc_small = [qc for qc in qc_ham_list if qc.num_qubits < 20]
qc_large = [qc for qc in qc_ham_list if qc.num_qubits >= 20]
print(f"小規模な回路(20 量子ビット未満): {len(qc_small)}")
print(f"大規模な回路(20 量子ビット以上): {len(qc_large)}")
Small-scale circuits (<20 qubits): 20
Large-scale circuits (>=20 qubits): 22
トランスパイル前に、小規模なハミルトニアン回路のうち 1 つを表示してみます。
# ここで回路を分解する。そうしないと PauliEvolutionGate の箱が 1 つ表示される
# だけで、見てもあまり情報が得られないため。
qc_small[0].decompose().draw("mpl", fold=-1)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/b4c5d6e7-0.avif" alt="Output of the previous code cell" />
小規模な例#
このセクションでは、20 量子ビット未満のハミルトニアン回路に対して 3 つのコンパイル手法をベンチマークします。これらの回路はトランスパイルが速く、中程度の複雑さの回路を各手法がどのように扱うかを明確に把握できます。
ステップ 1: 古典的な入力を量子問題にマッピングする#
各ハミルトニアンは PauliEvolutionGate 回路として符号化されます。回路はセットアップのセクションで、Hamlib のベンチマークデータからすでに構成済みです。
ステップ 2: 量子ハードウェアでの実行に向けて問題を最適化する#
3 つのパスマネージャーそれぞれで小規模な回路すべてをトランスパイルし、指標を収集します。
results_small = []
tqc_sabre_small = capture_transpilation_metrics(
results_small, pm_sabre, qc_small, "SABRE"
)
tqc_ai_small = capture_transpilation_metrics(
results_small, pm_ai, qc_small, "AI"
)
tqc_rustiq_small = capture_transpilation_metrics(
results_small, pm_rustiq, qc_small, "Rustiq"
)
[SABRE] Circuit 0 (all-vib-bh): 2Q depth=3, size=30, time=2.09s
[SABRE] Circuit 1 (all-vib-c2h): 2Q depth=18, size=111, time=0.01s
[SABRE] Circuit 2 (all-vib-o3): 2Q depth=6, size=58, time=0.00s
[SABRE] Circuit 3 (all-vib-c2h): 2Q depth=2, size=37, time=0.01s
[SABRE] Circuit 4 (graph-gnp_k-2): 2Q depth=24, size=126, time=0.01s
[SABRE] Circuit 5 (LiH): 2Q depth=66, size=285, time=0.01s
[SABRE] Circuit 6 (all-vib-fccf): 2Q depth=66, size=339, time=0.01s
[SABRE] Circuit 7 (all-vib-ch2): 2Q depth=88, size=413, time=0.01s
[SABRE] Circuit 8 (all-vib-f2): 2Q depth=180, size=1000, time=0.02s
[SABRE] Circuit 9 (all-vib-bhf2): 2Q depth=18, size=223, time=0.03s
[SABRE] Circuit 10 (graph-gnp_k-4): 2Q depth=122, size=675, time=0.02s
[SABRE] Circuit 11 (Be2): 2Q depth=343, size=1628, time=0.03s
[SABRE] Circuit 12 (all-vib-fccf): 2Q depth=14, size=134, time=0.00s
[SABRE] Circuit 13 (uf20-ham): 2Q depth=50, size=341, time=0.01s
[SABRE] Circuit 14 (TSP_Ncity-4): 2Q depth=118, size=615, time=0.01s
[SABRE] Circuit 15 (graph-complete_bipart): 2Q depth=232, size=1420, time=0.03s
[SABRE] Circuit 16 (all-vib-cyclo_propene): 2Q depth=18, size=354, time=0.93s
[SABRE] Circuit 17 (all-vib-hno): 2Q depth=6, size=174, time=0.14s
[SABRE] Circuit 18 (all-vib-fccf): 2Q depth=30, size=286, time=0.01s
[SABRE] Circuit 19 (tfim): 2Q depth=31, size=232, time=0.03s
[AI] Circuit 0 (all-vib-bh): 2Q depth=3, size=30, time=0.01s
[AI] Circuit 1 (all-vib-c2h): 2Q depth=18, size=101, time=0.18s
[AI] Circuit 2 (all-vib-o3): 2Q depth=6, size=58, time=0.01s
[AI] Circuit 3 (all-vib-c2h): 2Q depth=2, size=37, time=0.01s
[AI] Circuit 4 (graph-gnp_k-2): 2Q depth=24, size=133, time=0.07s
[AI] Circuit 5 (LiH): 2Q depth=62, size=267, time=8.00s
[AI] Circuit 6 (all-vib-fccf): 2Q depth=65, size=300, time=0.18s
[AI] Circuit 7 (all-vib-ch2): 2Q depth=79, size=353, time=0.16s
[AI] Circuit 8 (all-vib-f2): 2Q depth=176, size=998, time=0.43s
[AI] Circuit 9 (all-vib-bhf2): 2Q depth=18, size=194, time=0.11s
[AI] Circuit 10 (graph-gnp_k-4): 2Q depth=114, size=668, time=0.18s
[AI] Circuit 11 (Be2): 2Q depth=292, size=1382, time=0.88s
[AI] Circuit 12 (all-vib-fccf): 2Q depth=14, size=134, time=0.01s
[AI] Circuit 13 (uf20-ham): 2Q depth=40, size=330, time=0.16s
[AI] Circuit 14 (TSP_Ncity-4): 2Q depth=96, size=600, time=0.29s
[AI] Circuit 15 (graph-complete_bipart): 2Q depth=231, size=1531, time=0.46s
[AI] Circuit 16 (all-vib-cyclo_propene): 2Q depth=18, size=309, time=0.25s
[AI] Circuit 17 (all-vib-hno): 2Q depth=10, size=198, time=0.15s
[AI] Circuit 18 (all-vib-fccf): 2Q depth=34, size=402, time=0.02s
[AI] Circuit 19 (tfim): 2Q depth=44, size=311, time=0.15s
[Rustiq] Circuit 0 (all-vib-bh): 2Q depth=3, size=30, time=0.01s
[Rustiq] Circuit 1 (all-vib-c2h): 2Q depth=13, size=69, time=0.00s
[Rustiq] Circuit 2 (all-vib-o3): 2Q depth=13, size=82, time=0.01s
[Rustiq] Circuit 3 (all-vib-c2h): 2Q depth=2, size=40, time=0.01s
[Rustiq] Circuit 4 (graph-gnp_k-2): 2Q depth=31, size=132, time=0.01s
[Rustiq] Circuit 5 (LiH): 2Q depth=59, size=285, time=0.01s
[Rustiq] Circuit 6 (all-vib-fccf): 2Q depth=34, size=193, time=0.00s
[Rustiq] Circuit 7 (all-vib-ch2): 2Q depth=49, size=302, time=0.01s
[Rustiq] Circuit 8 (all-vib-f2): 2Q depth=141, size=807, time=0.02s
[Rustiq] Circuit 9 (all-vib-bhf2): 2Q depth=13, size=146, time=0.02s
[Rustiq] Circuit 10 (graph-gnp_k-4): 2Q depth=129, size=683, time=0.02s
[Rustiq] Circuit 11 (Be2): 2Q depth=220, size=1101, time=0.02s
[Rustiq] Circuit 12 (all-vib-fccf): 2Q depth=53, size=333, time=0.01s
[Rustiq] Circuit 13 (uf20-ham): 2Q depth=63, size=425, time=0.01s
[Rustiq] Circuit 14 (TSP_Ncity-4): 2Q depth=123, size=767, time=0.02s
[Rustiq] Circuit 15 (graph-complete_bipart): 2Q depth=309, size=2107, time=0.05s
[Rustiq] Circuit 16 (all-vib-cyclo_propene): 2Q depth=16, size=283, time=0.32s
[Rustiq] Circuit 17 (all-vib-hno): 2Q depth=19, size=291, time=0.32s
[Rustiq] Circuit 18 (all-vib-fccf): 2Q depth=44, size=546, time=0.02s
[Rustiq] Circuit 19 (tfim): 2Q depth=24, size=416, time=0.01s
次の表は、小規模な回路すべてにわたる各指標の平均と標準偏差、および SABRE に対する改善率をまとめたものです。回路サイズには大きなばらつきがあるため、標準偏差は平均を解釈するうえで重要な手がかりになります。
print_summary_table(results_small)
Mean +/- std per compilation method
Method 2Q Depth Gate Count Runtime (s)
------------------------------------------------------------------------------
SABRE 71.8 +/- 89.6 424.1 +/- 446.0 0.2 +/- 0.5
AI 67.3 +/- 80.2 416.8 +/- 426.7 0.6 +/- 1.8
Rustiq 67.9 +/- 80.0 451.9 +/- 484.7 0.0 +/- 0.1
Mean % improvement vs SABRE (positive = better than SABRE)
Method 2Q Depth Gate Count Runtime (s)
------------------------------------------------------------------------------
AI -2.1% +/- 19.8% -0.6% +/- 14.7% -5635.1% +/- 20725.2%
Rustiq -25.3% +/- 85.4% -16.3% +/- 50.4% -7.0% +/- 60.6%
回路ごとの表は、個々の回路で各手法がどのように比較されるかを示します。各指標の最良値にはアスタリスクが付いています。最も単純な回路では、3 つの手法が同じ結果に収束することが多い点に注目してください。
print_per_circuit_comparison(results_small, num_rows=8)
2Q Depth (first 8 circuits by qubit count); * = best
Idx Circuit Q SABRE AI Rustiq
----------------------------------------------------
0 all-vib-bh 2 3* 3* 3*
1 all-vib-c2h 3 18 18 13*
2 all-vib-o3 4 6* 6* 13
3 all-vib-c2h 4 2* 2* 2*
4 graph-gnp_k-2 4 24* 24* 31
5 LiH 4 66 62 59*
6 all-vib-fccf 4 66 65 34*
7 all-vib-ch2 4 88 79 49*
Gate Count (first 8 circuits by qubit count); * = best
Idx Circuit Q SABRE AI Rustiq
----------------------------------------------------
0 all-vib-bh 2 30* 30* 30*
1 all-vib-c2h 3 111 101 69*
2 all-vib-o3 4 58* 58* 82
3 all-vib-c2h 4 37* 37* 40
4 graph-gnp_k-2 4 126* 133 132
5 LiH 4 285 267* 285
6 all-vib-fccf 4 339 300 193*
7 all-vib-ch2 4 413 353 302*
Runtime (s) (first 8 circuits by qubit count); * = best
Idx Circuit Q SABRE AI Rustiq
----------------------------------------------------
0 all-vib-bh 2 2.09 0.01 0.01*
1 all-vib-c2h 3 0.01 0.18 0.00*
2 all-vib-o3 4 0.00* 0.01 0.01
3 all-vib-c2h 4 0.01 0.01 0.01*
4 graph-gnp_k-2 4 0.01* 0.07 0.01
5 LiH 4 0.01* 8.00 0.01
6 all-vib-fccf 4 0.01 0.18 0.00*
7 all-vib-ch2 4 0.01 0.16 0.01*
結果の可視化#
以下のプロットは、回路ごとに各指標で 3 つの手法を比較したものです。回路は量子ビット数でソートし、x 軸ではインデックスでラベル付けしています(複数の回路が同じ量子ビット数を持ち得るためです)。
def plot_transpilation_comparison(results, title_prefix):
"""
2 量子ビット深さ、回路サイズ、実行時間についてコンパイル手法を比較する
3 パネルの図を作成する。
回路は量子ビット数でソートし、回路インデックスに沿ってプロットする。
"""
methods = _method_order(results)
palette = {"SABRE": "#1f77b4", "AI": "#ff7f0e", "Rustiq": "#2ca02c"}
markers = {"SABRE": "o", "AI": "^", "Rustiq": "s"}
# 回路を量子ビット数(次にインデックス)で並べ、プロット位置に対応付ける
ref = sorted(
[r for r in results if r["method"] == methods[0]],
key=lambda r: (r["num_qubits"], r["qc_index"]),
)
pos_map = {r["qc_index"]: pos for pos, r in enumerate(ref)}
tick_positions = [pos_map[r["qc_index"]] for r in ref]
tick_labels = [
f"{pos_map[r['qc_index']]} ({r['num_qubits']}q)" for r in ref
]
metrics = [
("two_qubit_depth", "2 量子ビット深さ"),
("size", "総ゲート数(回路サイズ)"),
("runtime", "トランスパイル実行時間 (s)"),
]
fig, axes = plt.subplots(1, 3, figsize=(20, 5.5))
fig.suptitle(title_prefix, fontsize=15, fontweight="bold", y=1.02)
for ax, (metric, ylabel) in zip(axes, metrics):
for method in methods:
subset = sorted(
[r for r in results if r["method"] == method],
key=lambda r: pos_map[r["qc_index"]],
)
ax.plot(
[pos_map[r["qc_index"]] for r in subset],
[r[metric] for r in subset],
marker=markers.get(method, "o"),
label=method,
color=palette.get(method, None),
linewidth=1.5,
markersize=6,
alpha=0.85,
)
ax.set_xlabel("回路インデックス(量子ビット数)", fontsize=11)
ax.set_ylabel(ylabel, fontsize=11)
ax.legend(frameon=True, fontsize=9)
ax.grid(True, linestyle="--", alpha=0.4)
step = max(1, len(tick_positions) // 15)
ax.set_xticks(tick_positions[::step])
ax.set_xticklabels(
[tick_labels[i] for i in range(0, len(tick_labels), step)],
fontsize=7,
rotation=45,
ha="right",
)
plt.tight_layout()
plt.show()
def plot_pct_improvement_vs_sabre(results, title_prefix):
"""
各指標について、SABRE 以外の各手法の SABRE に対する改善率を回路ごとに
プロットする。正の値はその手法が SABRE を上回ったことを、負の値は
SABRE のほうが良かったことを意味する。
"""
metrics = [
("two_qubit_depth", "2Q 深さ"),
("size", "ゲート数"),
("runtime", "実行時間"),
]
palette = {"AI": "#ff7f0e", "Rustiq": "#2ca02c"}
markers = {"AI": "^", "Rustiq": "s"}
methods = _method_order(results)
sabre = sorted(
[r for r in results if r["method"] == "SABRE"],
key=lambda r: (r["num_qubits"], r["qc_index"]),
)
other_methods = [m for m in methods if m != "SABRE"]
tick_positions = list(range(len(sabre)))
tick_labels = [
f"{i} ({sabre[i]['num_qubits']}q)" for i in range(len(sabre))
]
fig, axes = plt.subplots(1, 3, figsize=(20, 5.5))
fig.suptitle(
f"{title_prefix}: SABRE に対する改善率 %",
fontsize=15,
fontweight="bold",
y=1.02,
)
for ax, (metric, label) in zip(axes, metrics):
ax.axhline(
0, color="#1f77b4", linewidth=2, label="SABRE(基準)"
)
for method in other_methods:
data = sorted(
[r for r in results if r["method"] == method],
key=lambda r: (r["num_qubits"], r["qc_index"]),
)
pct = [
(sabre[i][metric] - data[i][metric]) / sabre[i][metric] * 100
for i in range(len(sabre))
]
ax.plot(
tick_positions,
pct,
marker=markers.get(method, "o"),
label=method,
color=palette.get(method, None),
linewidth=1.5,
markersize=6,
alpha=0.85,
)
ax.set_xlabel("回路インデックス(量子ビット数)", fontsize=11)
ax.set_ylabel(f"改善率 % ({label})", fontsize=11)
ax.legend(frameon=True, fontsize=9)
ax.grid(True, linestyle="--", alpha=0.4)
step = max(1, len(tick_positions) // 15)
ax.set_xticks(tick_positions[::step])
ax.set_xticklabels(
[tick_labels[i] for i in range(0, len(tick_labels), step)],
fontsize=7,
rotation=45,
ha="right",
)
ylims = ax.get_ylim()
ax.axhspan(0, max(ylims[1], 1), alpha=0.04, color="green")
ax.axhspan(min(ylims[0], -1), 0, alpha=0.04, color="red")
plt.tight_layout()
plt.show()
plot_transpilation_comparison(
results_small,
"小規模なハミルトニアン回路: コンパイルの比較",
)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/d5e6f7a8-0.avif" alt="Output of the previous code cell" />
plot_pct_improvement_vs_sabre(
results_small,
"小規模なハミルトニアン回路",
)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/pct_improvement_small-0.avif" alt="Output of the previous code cell" />
この規模では 3 つのパスマネージャーはいずれも良好に動作し、平均的な結果は互いに近い値になります。これは主に、小さな回路にはさらなる最適化の余地が限られており、各手法が似た解に収束しやすいためです。
この例では Rustiq の結果が最もばらつきが大きく、2 量子ビット深さとゲート数の双方で最大の外れ値が生じています。このばらつきは、時に他の手法に劣ることを意味する一方で、Rustiq が他の 2 手法よりも良い解を見つけることがあることも意味します。AI トランスパイラーは SABRE や Rustiq に比べて結果が安定しており、外れ値も少なく、ほとんどの回路で他の手法に近い値で推移します。
実行時間については、SABRE と Rustiq がいずれも高速で、AI 搭載トランスパイラーは一部の回路で明らかに低速です。
指標ごとの最良手法#
次のチャートは、各指標で最良(最小)の値を達成した回数が手法ごとにどれくらいあったかを示します。同値になることもあり得ます。単純な回路では、複数の手法が同じ最適な 2 量子ビット深さやゲート数に到達し得ます。同値の場合は同値となったすべての手法に加点されるため、ある指標の割合の合計が 100% を超えることがあります。
def plot_best_method_bars(results, metrics_list=None):
"""
各指標で最良(最小)の値を達成した回路の割合を手法ごとに示す、
グループ化された棒グラフをプロットする。
同値の場合は同値となったすべての手法が計上されるため、指標ごとの割合の
合計が 100% を超えることがある。
"""
if metrics_list is None:
metrics_list = ["two_qubit_depth", "size", "runtime"]
labels = {
"two_qubit_depth": "2Q 深さ",
"size": "ゲート数",
"runtime": "実行時間",
}
methods = _method_order(results)
palette = {"SABRE": "#1f77b4", "AI": "#ff7f0e", "Rustiq": "#2ca02c"}
by_index = {}
for r in results:
by_index.setdefault(r["qc_index"], []).append(r)
n_circuits = len(by_index)
win_data = {m: [] for m in methods}
tie_counts = []
metric_labels = []
for metric in metrics_list:
metric_labels.append(
labels.get(metric, metric.replace("_", " ").title())
)
counts = Counter()
ties = 0
for group in by_index.values():
min_val = min(r[metric] for r in group)
best = [r["method"] for r in group if r[metric] == min_val]
if len(best) > 1:
ties += 1
counts.update(best)
tie_counts.append(ties)
for m in methods:
win_data[m].append(counts.get(m, 0) / n_circuits * 100)
x = np.arange(len(metric_labels))
width = 0.22
fig, ax = plt.subplots(figsize=(8, 5))
for i, method in enumerate(methods):
bars = ax.bar(
x + i * width,
win_data[method],
width,
label=method,
color=palette.get(method, None),
edgecolor="black",
linewidth=0.5,
)
for bar in bars:
height = bar.get_height()
if height > 0:
ax.text(
bar.get_x() + bar.get_width() / 2,
height + 1.5,
f"{height:.0f}%",
ha="center",
va="bottom",
fontsize=9,
)
# 各指標のラベルの下に同値の件数を注記する
for j, ties in enumerate(tie_counts):
if ties > 0:
ax.text(
x[j] + width,
-8,
f"(同値 {ties} 件)",
ha="center",
va="top",
fontsize=8,
color="gray",
)
ax.set_xticks(x + width)
ax.set_xticklabels(metric_labels, fontsize=11)
ax.set_ylabel("最良値となった回路の割合 (%)", fontsize=11)
ax.set_title(
"指標ごとの最良手法(同値の場合は同値のすべての手法を計上)",
fontsize=12,
fontweight="bold",
)
ax.legend(frameon=True, fontsize=10)
ax.set_ylim(-12, 120)
ax.yaxis.set_major_formatter(ticker.PercentFormatter())
ax.grid(axis="y", linestyle="--", alpha=0.4)
plt.tight_layout()
plt.show()
plot_best_method_bars(results_small)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/a6b7c8d9-0.avif" alt="Output of the previous code cell" />
この例では、小規模な回路において 3 つの手法は非常に近い性能を示しています。2 量子ビット深さとゲート数では、各手法が最良となる回路の割合は近い値(おおよそ 35〜55%)であり、最も単純な回路では複数の手法が見つけ得る唯一の最適解が存在することが多いため、多くの回路が同値で終わります。最も明確な差は実行時間です。SABRE と Rustiq はそれぞれ約半数の回路で最速となる一方、AI 搭載トランスパイラーが最速となることはほとんどありません。3 つの指標を合わせて見ると、Rustiq が全体としてわずかに優位です。2 量子ビット深さで最も多く勝ち、ゲート数と実行時間でも競争力を保っています。
ステップ 3: Qiskit プリミティブを使って実行する#
トランスパイルの品質がノイズ下での実行にどう影響するかを評価するため、ミラー回路 の手法を使います。トランスパイル後の各回路 \(U\) に対してその逆回路 \(U^\dagger\) を付加し、合成した回路 \(U^\dagger U\) が理論上は恒等演算になるようにします。\(|0\rangle\) 状態から始めれば、理想的な(ノイズのない)実行では確率 1 ですべて 0 のビット列が返るはずです。
実際にはゲートエラーが回路全体にわたって蓄積するため、\(|0\rangle^{\otimes n}\) を回復する確率は低下します。より浅く、ゲート数の少ない回路を生成するコンパイル手法では、蓄積するノイズが少なくなります。
ミラー回路のアプローチは、期待される出力が常に \(|0\rangle^{\otimes n}\) であり理想状態の古典シミュレーションを必要としないため、単純で扱いやすく、どんな回路サイズにもスケールします。ただし次の注意点があります。ミラー回路は実際の回路そのものではなく、その代理であること、ゲート数が 2 倍になる(そのためノイズの影響が過大に現れる)こと、そしてミラーの境界を挟んでノイズが対称的に打ち消し合う場合には一部のエラーを過小評価し得ることです。
ここでは小規模な回路のセットからインデックス 6 の回路を選び、単純な脱分極ノイズモデルを設定した Aer シミュレーターでミラー回路を実行します。
# 小規模なトランスパイル済み回路からインデックス 6 の回路を選ぶ
test_idx = 6
test_circuit = qc_small[test_idx]
print(f"テスト回路: {test_circuit.name}, {test_circuit.num_qubits} 量子ビット")
# トランスパイル後のバージョンを取得する
tqc_methods_small = {
"SABRE": tqc_sabre_small[test_idx],
"AI": tqc_ai_small[test_idx],
"Rustiq": tqc_rustiq_small[test_idx],
}
# この回路のトランスパイル指標を表示する
print(f"\n回路インデックス {test_idx} のトランスパイル指標:")
for method, tqc in tqc_methods_small.items():
depth_2q = tqc.depth(lambda x: x.operation.num_qubits == 2)
size = tqc.size()
print(f" {method:8s} 2Q 深さ={depth_2q:5d} サイズ={size:6d}")
Test circuit: all-vib-fccf, 4 qubits
Transpilation metrics for circuit index 6:
SABRE 2Q depth= 66 size= 339
AI 2Q depth= 65 size= 300
Rustiq 2Q depth= 34 size= 193
ミラー回路(\(U^\dagger\) を付加したもの)を構成し、シミュレーターが実際に使用する量子ビットのみを扱うように量子ビットのインデックスを連続した番号に振り直したうえで、ノイズあり Aer シミュレーターで実行します。
def remap_to_contiguous(tqc):
"""トランスパイル後の回路を、連続した量子ビットインデックスに振り直す。
トランスパイル後の回路は、大きなバックエンド上の特定の物理量子ビット
(例: 量子ビット 45、67)を対象とする。ここではそれらを 0, 1, 2, ...
に振り直し、Aer が実際に使用する量子ビットのみをシミュレートするように
する。
"""
active = sorted(
{tqc.find_bit(q).index for inst in tqc.data for q in inst.qubits}
)
qubit_map = {old: new for new, old in enumerate(active)}
new_qc = QuantumCircuit(len(active))
for inst in tqc.data:
old_indices = [tqc.find_bit(q).index for q in inst.qubits]
new_qc.append(inst.operation, [qubit_map[i] for i in old_indices])
return new_qc
def build_mirror_circuit(tqc):
"""ミラー回路を構成する: U の後に U-dagger を続け、測定を付加する。
合成した回路 U-dagger @ U は恒等演算になるはずなので、すべて 0 が
測定されればノイズのない実行であることを示す。
"""
tqc_compact = remap_to_contiguous(tqc)
mirror = tqc_compact.compose(tqc_compact.inverse())
mirror.measure_all()
return mirror
# 単純な脱分極ノイズモデルを構成する
noise_model = NoiseModel()
noise_model.add_all_qubit_quantum_error(
depolarizing_error(0.001, 1),
["sx", "x", "rz"], # 1 量子ビットゲートあたり約 0.1%
)
noise_model.add_all_qubit_quantum_error(
depolarizing_error(0.01, 2),
["cx", "ecr"], # 2 量子ビットゲートあたり約 1%
)
aer_sim = AerSimulator(noise_model=noise_model)
shots = 10000
fidelities = {}
for method, tqc in tqc_methods_small.items():
mirror = build_mirror_circuit(tqc)
sampler = SamplerV2(mode=aer_sim)
job = sampler.run([mirror], shots=shots)
result = job.result()
counts = result[0].data.meas.get_counts()
# 忠実度 = すべて 0(エラーなし)の結果の割合
n_qubits = mirror.num_qubits - mirror.num_clbits # 使用する量子ビット
all_zeros = "0" * mirror.num_qubits
fidelity = counts.get(all_zeros, 0) / shots
fidelities[method] = fidelity
print(
f"{method:8s} P(|00...0>) = {fidelity:.4f} "
f"({counts.get(all_zeros, 0)}/{shots})"
)
SABRE P(|00...0>) = 0.7796 (7796/10000)
AI P(|00...0>) = 0.8073 (8073/10000)
Rustiq P(|00...0>) = 0.8923 (8923/10000)
def plot_mirror_results(tqc_methods, fidelities, circuit_name):
"""
コンパイル手法ごとに、忠実度、2Q 深さ、ゲート数を比較する 3 パネルの
図をプロットする。
"""
methods = list(tqc_methods.keys())
palette = {"SABRE": "#1f77b4", "AI": "#ff7f0e", "Rustiq": "#2ca02c"}
colors = [palette.get(m, "gray") for m in methods]
fidelity_vals = [fidelities[m] for m in methods]
depth_vals = [
tqc_methods[m].depth(lambda x: x.operation.num_qubits == 2)
for m in methods
]
size_vals = [tqc_methods[m].size() for m in methods]
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
fig.suptitle(
f"ミラー回路の結果: {circuit_name}",
fontsize=14,
fontweight="bold",
y=1.02,
)
def _annotate_bars(ax, bars, values, fmt="{}"):
ymax = ax.get_ylim()[1]
for bar, val in zip(bars, values):
label = fmt.format(val)
y = val + ymax * 0.03
ax.text(
bar.get_x() + bar.get_width() / 2,
y,
label,
ha="center",
va="bottom",
fontsize=10,
fontweight="bold",
)
# パネル 1: 生存確率
bars = axes[0].bar(
methods, fidelity_vals, color=colors, edgecolor="black", linewidth=0.5
)
axes[0].set_ylabel("忠実度 P(|00...0>)", fontsize=11)
axes[0].set_title("忠実度(高いほど良い)", fontsize=12)
axes[0].set_ylim(
0, max(fidelity_vals) * 1.18 if max(fidelity_vals) > 0 else 1.0
)
axes[0].grid(axis="y", linestyle="--", alpha=0.4)
_annotate_bars(axes[0], bars, fidelity_vals, fmt="{:.4f}")
# パネル 2: 2 量子ビット深さ
bars = axes[1].bar(
methods, depth_vals, color=colors, edgecolor="black", linewidth=0.5
)
axes[1].set_ylabel("2 量子ビット深さ", fontsize=11)
axes[1].set_title("2Q 深さ(低いほど良い)", fontsize=12)
axes[1].set_ylim(0, max(depth_vals) * 1.18)
axes[1].grid(axis="y", linestyle="--", alpha=0.4)
_annotate_bars(axes[1], bars, depth_vals)
# パネル 3: ゲート数
bars = axes[2].bar(
methods, size_vals, color=colors, edgecolor="black", linewidth=0.5
)
axes[2].set_ylabel("総ゲート数", fontsize=11)
axes[2].set_title("ゲート数(低いほど良い)", fontsize=12)
axes[2].set_ylim(0, max(size_vals) * 1.18)
axes[2].grid(axis="y", linestyle="--", alpha=0.4)
_annotate_bars(axes[2], bars, size_vals)
plt.tight_layout()
plt.show()
plot_mirror_results(tqc_methods_small, fidelities, test_circuit.name)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/small_step4_plot-0.avif" alt="Output of the previous code cell" />
考察#
2 量子ビット深さが最も小さくゲート数も最も少ない手法が最も高い忠実度を達成しており、回路が短いほど蓄積するノイズが少なくなるという予想と整合しています。深さとゲート数のわずかな差であっても、脱分極ノイズモデルの下では測定可能な忠実度の差として現れます。
これらの結果は単一の回路に対するものであることに留意してください。手法間の相対的な順位は、ハミルトニアンの構造に応じて回路ごとに変わり得ます。
大規模なハードウェアでの例#
このセクションでは、20 量子ビット以上のハミルトニアン回路に対して、同じ 3 つのコンパイル手法をベンチマークします。これらの回路は実用的なハミルトニアンシミュレーションのワークロードをより代表するものであり、回路品質とコンパイル時間の観点で各手法のスケール性を試すものです。
ステップ 1〜4 をまとめて実行#
ワークフローは小規模な例と同じ構成です。各手法で大規模な回路すべてをトランスパイルし、指標を収集して、ミラー回路を実機の量子ハードウェアに投入します。
results_large = []
tqc_sabre_large = capture_transpilation_metrics(
results_large, pm_sabre, qc_large, "SABRE"
)
tqc_ai_large = capture_transpilation_metrics(
results_large, pm_ai, qc_large, "AI"
)
tqc_rustiq_large = capture_transpilation_metrics(
results_large, pm_rustiq, qc_large, "Rustiq"
)
[SABRE] Circuit 0 (all-vib-hc3h2cn): 2Q depth=2, size=258, time=0.16s
[SABRE] Circuit 1 (ham-graph-gnp_k-5): 2Q depth=345, size=4036, time=0.08s
[SABRE] Circuit 2 (TSP_Ncity-5): 2Q depth=187, size=2045, time=0.04s
[SABRE] Circuit 3 (tfim): 2Q depth=100, size=489, time=0.21s
[SABRE] Circuit 4 (all-vib-h2co): 2Q depth=30, size=570, time=0.18s
[SABRE] Circuit 5 (uuf100-ham): 2Q depth=414, size=4779, time=0.09s
[SABRE] Circuit 6 (uuf100-ham): 2Q depth=523, size=5667, time=0.11s
[SABRE] Circuit 7 (graph-gnp_k-4): 2Q depth=3028, size=24885, time=0.39s
[SABRE] Circuit 8 (uf100-ham): 2Q depth=700, size=8271, time=0.15s
[SABRE] Circuit 9 (uf100-ham): 2Q depth=698, size=8957, time=0.15s
[SABRE] Circuit 10 (TSP_Ncity-7): 2Q depth=432, size=6353, time=0.12s
[SABRE] Circuit 11 (all-vib-cyclo_propene): 2Q depth=30, size=1144, time=0.20s
[SABRE] Circuit 12 (TSP_Ncity-8): 2Q depth=704, size=10287, time=0.18s
[SABRE] Circuit 13 (uf100-ham): 2Q depth=2454, size=30195, time=0.46s
[SABRE] Circuit 14 (tfim): 2Q depth=245, size=3670, time=0.08s
[SABRE] Circuit 15 (flat100-ham): 2Q depth=154, size=3836, time=0.12s
[SABRE] Circuit 16 (graph-regular_reg-4): 2Q depth=863, size=14063, time=0.22s
[SABRE] Circuit 17 (tfim): 2Q depth=581, size=8810, time=0.15s
[SABRE] Circuit 18 (FH_D-1): 2Q depth=1704, size=9528, time=0.35s
[SABRE] Circuit 19 (TSP_Ncity-10): 2Q depth=1091, size=22041, time=0.38s
[SABRE] Circuit 20 (TSP_Ncity-10): 2Q depth=1091, size=22005, time=0.38s
[SABRE] Circuit 21 (ham-unary-color02-queen13_13_k-4): 2Q depth=224, size=8321, time=0.17s
[AI] Circuit 0 (all-vib-hc3h2cn): 2Q depth=2, size=258, time=0.17s
[AI] Circuit 1 (ham-graph-gnp_k-5): 2Q depth=323, size=4418, time=3.13s
[AI] Circuit 2 (TSP_Ncity-5): 2Q depth=161, size=2229, time=1.47s
[AI] Circuit 3 (tfim): 2Q depth=20, size=402, time=0.34s
[AI] Circuit 4 (all-vib-h2co): 2Q depth=38, size=661, time=0.19s
[AI] Circuit 5 (uuf100-ham): 2Q depth=391, size=5130, time=3.27s
[AI] Circuit 6 (uuf100-ham): 2Q depth=463, size=6095, time=4.23s
[AI] Circuit 7 (graph-gnp_k-4): 2Q depth=3207, size=25641, time=15.15s
[AI] Circuit 8 (uf100-ham): 2Q depth=637, size=8267, time=5.87s
[AI] Circuit 9 (uf100-ham): 2Q depth=632, size=9330, time=7.29s
[AI] Circuit 10 (TSP_Ncity-7): 2Q depth=452, size=7418, time=6.02s
[AI] Circuit 11 (all-vib-cyclo_propene): 2Q depth=38, size=1323, time=0.27s
[AI] Circuit 12 (TSP_Ncity-8): 2Q depth=609, size=11131, time=10.07s
[AI] Circuit 13 (uf100-ham): 2Q depth=2251, size=31128, time=38.77s
[AI] Circuit 14 (tfim): 2Q depth=165, size=3460, time=1.64s
[AI] Circuit 15 (flat100-ham): 2Q depth=91, size=3497, time=2.49s
[AI] Circuit 16 (graph-regular_reg-4): 2Q depth=664, size=15256, time=12.35s
[AI] Circuit 17 (tfim): 2Q depth=583, size=9157, time=6.28s
[AI] Circuit 18 (FH_D-1): 2Q depth=1193, size=7754, time=4.54s
[AI] Circuit 19 (TSP_Ncity-10): 2Q depth=1134, size=22577, time=25.64s
[AI] Circuit 20 (TSP_Ncity-10): 2Q depth=1172, size=23851, time=28.97s
[AI] Circuit 21 (ham-unary-color02-queen13_13_k-4): 2Q depth=219, size=8600, time=8.85s
[Rustiq] Circuit 0 (all-vib-hc3h2cn): 2Q depth=2, size=257, time=0.16s
[Rustiq] Circuit 1 (ham-graph-gnp_k-5): 2Q depth=640, size=5831, time=0.13s
[Rustiq] Circuit 2 (TSP_Ncity-5): 2Q depth=408, size=3985, time=0.08s
[Rustiq] Circuit 3 (tfim): 2Q depth=31, size=688, time=0.07s
[Rustiq] Circuit 4 (all-vib-h2co): 2Q depth=65, size=1058, time=2.91s
[Rustiq] Circuit 5 (uuf100-ham): 2Q depth=633, size=6757, time=0.14s
[Rustiq] Circuit 6 (uuf100-ham): 2Q depth=795, size=8495, time=0.17s
[Rustiq] Circuit 7 (graph-gnp_k-4): 2Q depth=13768, size=139793, time=2.92s
[Rustiq] Circuit 8 (uf100-ham): 2Q depth=1099, size=11878, time=0.25s
[Rustiq] Circuit 9 (uf100-ham): 2Q depth=911, size=11111, time=0.22s
[Rustiq] Circuit 10 (TSP_Ncity-7): 2Q depth=1183, size=13197, time=0.27s
[Rustiq] Circuit 11 (all-vib-cyclo_propene): 2Q depth=67, size=2491, time=13.56s
[Rustiq] Circuit 12 (TSP_Ncity-8): 2Q depth=1615, size=21358, time=0.48s
[Rustiq] Circuit 13 (uf100-ham): 2Q depth=2920, size=40465, time=0.91s
[Rustiq] Circuit 14 (tfim): 2Q depth=489, size=6552, time=0.15s
[Rustiq] Circuit 15 (flat100-ham): 2Q depth=378, size=5906, time=0.14s
[Rustiq] Circuit 16 (graph-regular_reg-4): 2Q depth=12163, size=168679, time=2.94s
[Rustiq] Circuit 17 (tfim): 2Q depth=1208, size=17042, time=0.36s
[Rustiq] Circuit 18 (FH_D-1): 2Q depth=1061, size=24000, time=0.47s
[Rustiq] Circuit 19 (TSP_Ncity-10): 2Q depth=2565, size=41340, time=1.38s
[Rustiq] Circuit 20 (TSP_Ncity-10): 2Q depth=2565, size=41275, time=1.38s
[Rustiq] Circuit 21 (ham-unary-color02-queen13_13_k-4): 2Q depth=808, size=17548, time=0.42s
print_summary_table(results_large)
Mean +/- std per compilation method
Method 2Q Depth Gate Count Runtime (s)
------------------------------------------------------------------------------
SABRE 709.1 +/- 783.8 9,100.5 +/- 8,493.1 0.2 +/- 0.1
AI 656.6 +/- 777.5 9,435.6 +/- 8,853.0 8.5 +/- 10.2
Rustiq 2,062.5 +/- 3,631.1 26,804.8 +/- 43,403.1 1.3 +/- 2.9
Mean % improvement vs SABRE (positive = better than SABRE)
Method 2Q Depth Gate Count Runtime (s)
------------------------------------------------------------------------------
AI +9.6% +/- 22.8% -3.4% +/- 9.4% -3620.0% +/- 2405.5%
Rustiq -154.5% +/- 273.9% -137.1% +/- 233.2% -527.0% +/- 1405.5%
print_per_circuit_comparison(results_large, num_rows=8)
2Q Depth (first 8 circuits by qubit count); * = best
Idx Circuit Q SABRE AI Rustiq
----------------------------------------------------
0 all-vib-hc3h2cn 24 2* 2* 2*
1 ham-graph-gnp_k- 24 345 323* 640
2 TSP_Ncity-5 25 187 161* 408
3 tfim 26 100 20* 31
4 all-vib-h2co 32 30* 38 65
5 uuf100-ham 40 414 391* 633
6 uuf100-ham 40 523 463* 795
7 graph-gnp_k-4 40 3028* 3207 13768
Gate Count (first 8 circuits by qubit count); * = best
Idx Circuit Q SABRE AI Rustiq
----------------------------------------------------
0 all-vib-hc3h2cn 24 258 258 257*
1 ham-graph-gnp_k- 24 4036* 4418 5831
2 TSP_Ncity-5 25 2045* 2229 3985
3 tfim 26 489 402* 688
4 all-vib-h2co 32 570* 661 1058
5 uuf100-ham 40 4779* 5130 6757
6 uuf100-ham 40 5667* 6095 8495
7 graph-gnp_k-4 40 24885* 25641 139793
Runtime (s) (first 8 circuits by qubit count); * = best
Idx Circuit Q SABRE AI Rustiq
----------------------------------------------------
0 all-vib-hc3h2cn 24 0.16 0.17 0.16*
1 ham-graph-gnp_k- 24 0.08* 3.13 0.13
2 TSP_Ncity-5 25 0.04* 1.47 0.08
3 tfim 26 0.21 0.34 0.07*
4 all-vib-h2co 32 0.18* 0.19 2.91
5 uuf100-ham 40 0.09* 3.27 0.14
6 uuf100-ham 40 0.11* 4.23 0.17
7 graph-gnp_k-4 40 0.39* 15.15 2.92
plot_transpilation_comparison(
results_large,
"大規模なハミルトニアン回路: コンパイルの比較",
)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/c7d8e9f0-0.avif" alt="Output of the previous code cell" />
plot_pct_improvement_vs_sabre(
results_large,
"大規模なハミルトニアン回路",
)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/pct_improvement_large-0.avif" alt="Output of the previous code cell" />
plot_best_method_bars(results_large)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/d7e8f9a0-0.avif" alt="Output of the previous code cell" />
# 大規模なトランスパイル済み回路からインデックス 3 の回路を選ぶ
test_idx_large = 3
test_circuit_large = qc_large[test_idx_large]
print(
f"テスト回路: {test_circuit_large.name}, {test_circuit_large.num_qubits} 量子ビット"
)
tqc_methods_large = {
"SABRE": tqc_sabre_large[test_idx_large],
"AI": tqc_ai_large[test_idx_large],
"Rustiq": tqc_rustiq_large[test_idx_large],
}
print(f"\n回路インデックス {test_idx_large} のトランスパイル指標:")
for method, tqc in tqc_methods_large.items():
depth_2q = tqc.depth(lambda x: x.operation.num_qubits == 2)
size = tqc.size()
print(f" {method:8s} 2Q 深さ={depth_2q:5d} サイズ={size:6d}")
Test circuit: tfim, 26 qubits
Transpilation metrics for circuit index 3:
SABRE 2Q depth= 100 size= 489
AI 2Q depth= 20 size= 402
Rustiq 2Q depth= 31 size= 688
pm_mirror = generate_preset_pass_manager(
optimization_level=0, backend=backend
)
for method, tqc in tqc_methods_large.items():
# 各回路の count_ops を表示する
mirror = tqc.copy()
mirror.compose(tqc.inverse(), inplace=True)
mirror.measure_all()
mirror = pm_mirror.run(mirror)
print(f"\n{method} のトランスパイル後の回路:")
print(tqc.count_ops())
print(f"{method} のミラー回路の count_ops:")
print(mirror.count_ops())
SABRE transpiled circuit:
OrderedDict({'sx': 211, 'rz': 163, 'cz': 104, 'x': 11})
SABRE mirror circuit count ops:
OrderedDict({'rz': 1170, 'sx': 422, 'cz': 208, 'measure': 156, 'x': 22, 'barrier': 1})
AI transpiled circuit:
OrderedDict({'sx': 165, 'rz': 162, 'cz': 68, 'x': 7})
AI mirror circuit count ops:
OrderedDict({'rz': 984, 'sx': 330, 'measure': 156, 'cz': 136, 'x': 14, 'barrier': 1})
Rustiq transpiled circuit:
OrderedDict({'sx': 316, 'rz': 225, 'cz': 140, 'x': 7})
Rustiq mirror circuit count ops:
OrderedDict({'rz': 1714, 'sx': 632, 'cz': 280, 'measure': 156, 'x': 14, 'barrier': 1})
# ミラー回路を構成して実機ハードウェアに投入する
# 逆回路によってバックエンドの基底ゲートセットに含まれないゲート
# (例: sxdg)が導入され得るため、ミラー回路を再トランスパイルする。
pm_mirror = generate_preset_pass_manager(
optimization_level=0, backend=backend
)
shots_hw = 10000
hw_jobs = {}
for method, tqc in tqc_methods_large.items():
mirror = tqc.copy()
mirror.compose(tqc.inverse(), inplace=True)
mirror.measure_all()
# レイアウトやルーティングを変えずに基底ゲートへ分解するため、
# 最適化レベル 0 で再トランスパイルする
mirror = pm_mirror.run(mirror)
sampler = SamplerV2(mode=backend)
sampler.options.environment.job_tags = ["TUT_CMHSC"]
job = sampler.run([mirror], shots=shots_hw)
hw_jobs[method] = job
print(f"{method}: ジョブ {job.job_id()} を投入しました")
SABRE: submitted job d8gvgq66983c73dqe5og
AI: submitted job d8gvgqe6983c73dqe5pg
Rustiq: submitted job d8gvgqm6983c73dqe5q0
# 結果を取得して忠実度を計算する
fidelities_large = {}
for method, job in hw_jobs.items():
result = job.result()
counts = result[0].data.meas.get_counts()
n_qubits = backend.num_qubits
all_zeros = "0" * n_qubits
fidelity = counts.get(all_zeros, 0) / shots_hw
fidelities_large[method] = fidelity
print(
f"{method:8s} P(|00...0>) = {fidelity:.4f} "
f"({counts.get(all_zeros, 0)}/{shots_hw})"
)
SABRE P(|00...0>) = 0.0005 (5/10000)
AI P(|00...0>) = 0.3267 (3267/10000)
Rustiq P(|00...0>) = 0.1845 (1845/10000)
plot_mirror_results(
tqc_methods_large, fidelities_large, test_circuit_large.name
)
<Image src="/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/large_hw_plot-0.avif" alt="Output of the previous code cell" />
コンパイル結果の分析#
上記のベンチマークでは、Hamlib コレクションのハミルトニアンシミュレーション回路に対して、小規模と大規模の両方の規模で SABRE、AI 搭載トランスパイラー、Rustiq を比較しました。
2 量子ビット深さとゲート数#
大規模では SABRE と AI 搭載トランスパイラーが最も優れた 2 つの手法であり、それぞれ異なる指標で先行します。指標ごとの最良手法 のチャートが示すとおり、SABRE は大多数の回路で最小のゲート数を生成し、ほぼすべての回路で最速の手法です。これは、挿入する SWAP ゲートの最小化を狙って設計されたヒューリスティックであること、そしてレイアウトとルーティングに対する近年の最適化と整合しています。AI 搭載トランスパイラーはほとんどの回路で最小の 2 量子ビット深さを生成し、これは回路の深さを対象とする強化学習の目的関数の一部と整合しています。サマリーの表も同じ住み分けを反映しており、SABRE は平均ゲート数がより小さく、AI トランスパイラーは平均 2 量子ビット深さがより小さくなっています。どちらの手法も、回路の全範囲にわたって安定していて信頼できます。
PauliEvolutionGate の合成に特化した Rustiq が単独で最良の結果を出すのは、大規模な回路のうちごく一部にとどまります。その平均的な指標は、いくつかの大きな外れ値によって大きく歪められています。これはコンパイル比較のプロットで大きなスパイクとして見え、そこでは Rustiq が他の手法よりも著しく高い深さとゲート数を生成しています。これらの外れ値がなければ、平均的な性能は SABRE や AI 搭載トランスパイラーにずっと近くなるはずです。
重要な知見は、すべての回路で他を圧倒する単一の手法は存在しないということです。各手法は特定の場合に他を上回るため、利用できるツールをすべて試して回路ごとに最良の結果を選ぶことに価値があります。
実行時間#
SABRE は一貫して最速の手法です。Rustiq は概ね同程度の速さで動作しますが、コンパイルに著しく長い時間を要する外れ値が生じることがあります。これは大規模の結果で特に顕著で、いくつかの回路で Rustiq の実行時間が急増します。これらの外れ値は平均実行時間に大きく影響するため、Rustiq については中央値のほうが代表的な要約になり得ます。AI 搭載トランスパイラーは 3 つの中で最も遅く、より大きく複雑な回路では実行時間が顕著に増加します。
ミラー回路の結果#
ミラー回路の実験は、予想どおりの傾向を裏付けています。2 量子ビット深さがより小さくゲート数がより少ない手法は、ノイズ下でより高い忠実度を達成します。これはノイズありシミュレーター(小規模)と実機ハードウェア(大規模)の両方で成り立ちます。
各ミラー回路のプロットは、全体の集計ではなく単一の回路を反映していることに留意してください。上記のハードウェアの例では 26 量子ビットの tfim 回路 1 つを使っていますが、これはたまたま SABRE が AI 搭載トランスパイラーや Rustiq よりもはるかに高い 2 量子ビット深さを生成するケースであり、そのため忠実度も相応にかなり低くなっています。これは全体的な結果を代表するものではありません。大規模な回路のセット全体では、SABRE の 2 量子ビット深さは通常 AI 搭載トランスパイラーのそれに近く、2 つの手法はそれぞれ異なる指標で先行します(2 量子ビット深さでは AI 搭載トランスパイラー、ゲート数と実行時間では SABRE)。単一のミラー結果は、ワークロード全体ではなく 1 つの回路を 2 倍にしたものを試しているだけなので、手法全体の品質に対する結論として読むべきではありません。
推奨#
すべての回路に対して最良となる単一のトランスパイル戦略は存在しません。最適な選択は、回路の構造、最適化の目標、そして使えるコンパイル時間の予算によって決まります。
SABRE が推奨されるデフォルトです。高速で信頼でき、幅広い回路にわたって良好な結果を生成します。さらに調整したい場合は、レイアウトとルーティングの試行回数を増やせます(SABRE 最適化のチュートリアル を参照)。
AI 搭載トランスパイラー は、コンパイル時間が制約にならない場合、特に 2 量子ビット深さの最小化が優先事項である場合に試す価値があります。このベンチマークでは、大規模な回路のほとんどで最小の 2 量子ビット深さを生成しました。
Rustiq は
PauliEvolutionGate回路に特化しており、特に小さめの回路では非常に低い深さ・少ないゲート数の解を見つけられます。より大きな回路では、時にはるかに大きな結果を生成し得るため、デフォルトとしてではなく、試す候補となる複数の手法の 1 つとして使うのが最適です。
実務上の最良のアプローチは、利用できるすべての手法を実行し、回路ごとに最良の結果を選ぶことです。複数の手法を試すコンパイルのオーバーヘッドは、実機ハードウェアでの実行品質が改善する可能性に比べれば小さいものです。
次のステップ#
このチュートリアルが役に立ったなら、次の内容にも興味を持たれるかもしれません。
参考文献#
[1] “LightSABRE: A Lightweight and Enhanced SABRE Algorithm”. H. Zou, M. Treinish, K. Hartman, A. Ivrii, J. Lishman et al. https://arxiv.org/abs/2409.08368
[2] “Practical and efficient quantum circuit synthesis and transpiling with Reinforcement Learning”. D. Kremer, V. Villar, H. Paik, I. Duran, I. Faro, J. Cruz-Benito et al. https://arxiv.org/abs/2405.13196
[3] “Pauli Network Circuit Synthesis with Reinforcement Learning”. A. Dubal, D. Kremer, S. Martiel, V. Villar, D. Wang, J. Cruz-Benito et al. https://arxiv.org/abs/2503.14448
[4] “Faster and shorter synthesis of Hamiltonian simulation circuits”. T. Goubault de Brugiere, S. Martiel et al. https://arxiv.org/abs/2404.03280
© IBM Corp., 2017-2026