Skip to main content
IBM Quantum Platform

ハミルトンシミュレーション回路のコンパイル手法

推定実行時間: IBM のHeronプロセッサで1分未満(注:これはあくまで推定値です。 (実行時間は異なる場合があります。)


学習成果

このチュートリアルを学習すると、以下のことが理解できるようになります:

  • SABRE を使用してレイアウトおよび配線最適化を行う際の Qiskit トランスパイラーの使い方
  • AIを活用したトランスパイラーを活用して、高度な回路最適化を行う方法
  • ハミルトニアンシミュレーション回路における演算を合成 PauliEvolutionGate するためのRustiqプラグインの使用方法
  • 2量子ビットの深さ、総ゲート数、および実行時間を用いて、コンパイル手法のベンチマークと比較を行う方法

前提条件

このチュートリアルを進める前に、以下のトピックについて理解しておいていただくことをお勧めします:


背景

量子回路のコンパイルとは、高水準の量子アルゴリズムを、対象となるハードウェアの制約を満たす物理回路に変換するプロセスである。 効果的なコンパイルにより、回路の深さとゲート数を大幅に削減することができ、これらはいずれも、近い将来の量子デバイスにおける結果の品質に直接的な影響を与える。

PauliEvolutionGateこのチュートリアルでは、で構築されたハミルトニアンシミュレーション回路について、3つのコンパイル手法のベンチマークを行います。 これらの回路は、量子ビット間のペアごとの相互作用( ZZZZXXXXYYYY 項など)をモデル化したものであり、量子化学、凝縮系物理学、材料科学の分野で広く用いられている。

ベンチマーク回路は、 Benchpress リポジトリを通じてアクセスできる Hamlib コレクションに収録されています。 Hamlibは、代表的なハミルトニアンの標準化されたセットを提供しており、これにより、現実的なシミュレーションワークロードにおいてコンパイル戦略を比較することが可能になります。

コンパイル方法の概要

SABRE 搭載の Qiskit トランスパイラー

Qiskitのトランスパイラーは、SABRE(SWAPベースの BidiREctional ヒューリスティック探索)アルゴリズムを用いて、回路のレイアウトと配線を最適化します。 SABREは、ハードウェアの接続制約を満たしつつ、SWAPゲートの数を最小限に抑え、回路の深さへの影響を最小限に抑えることに重点を置いています。 これは、パフォーマンスとコンパイル時間のバランスが良好な汎用的な手法です。 詳細については、 [1] を参照のこと。 SABREの利点やパラメータの検討については、別のチュートリアルで詳しく解説しています。

AIを活用したトランスパイラー

このAI搭載のトランスパイラーは、機械学習を活用して、回路構造やハードウェアの制約条件におけるパターンを分析し、最適なトランスパイリング戦略を予測します。 また、強化学習に基づく合成手法を用いてパウリネットワーク回路を対象とする「pass」を 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 トランスパイラー (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

# Suppress noisy loggers and warnings
logging.getLogger(
    "qiskit_ibm_transpiler.wrappers.ai_local_synthesis"
).setLevel(logging.ERROR)
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category=SparseEfficiencyWarning)

seed = 42  # Seed for reproducibility

バックエンドに接続する

小規模および大規模の例の両方で使用するバックエンドを選択してください。 バックエンドは、トランスパイラーが対象とする結合マップと基底ゲートを決定します。

# 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"Using backend: {backend.name}")

Output:

Using backend: ibm_pittsburgh

パスマネージャーの定義

3つのコンパイル方法を設定してください。

# SABRE pass manager (Qiskit default at optimization level 3)
pm_sabre = generate_preset_pass_manager(
    optimization_level=3, backend=backend, seed_transpiler=seed
)
# AI transpiler pass manager (local mode)
pm_ai = generate_ai_pass_manager(
    backend=backend, optimization_level=3, ai_optimization_level=3
)

Output:

Fetching 127 files:   0%|          | 0/127 [00:00<?, ?it/s]
# Rustiq pass manager for PauliEvolutionGate synthesis
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
):
    """
    Transpile circuits and append one metrics record per circuit to
    ``results``.

    Args:
        results (list): List of dicts to append the metrics records to.
        pass_manager: Pass manager used for transpilation.
        circuits (list): List of quantum circuits to transpile.
        method_name (str): Name of the transpilation method.

    Returns:
        list: List of transpiled circuits.
    """
    transpiled_circuits = []

    for i, qc in enumerate(circuits):
        start_time = time.time()
        transpiled_qc = pass_manager.run(qc)
        end_time = time.time()

        # Decompose swaps for consistency across methods
        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}] Circuit {i} ({qc.name}): "
            f"2Q depth={two_qubit_depth}, size={circuit_size}, "
            f"time={transpilation_time:.2f}s"
        )

    return transpiled_circuits
def _method_order(results):
    """Return the distinct method names in their first-seen order."""
    order = []
    for r in results:
        if r["method"] not in order:
            order.append(r["method"])
    return order


def print_summary_table(results):
    """
    Print the mean and standard deviation of each metric per compilation
    method, followed by the mean percent improvement relative to SABRE.
    """
    metrics = [
        ("two_qubit_depth", "2Q Depth"),
        ("size", "Gate Count"),
        ("runtime", "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"{'Method':<{name_w}}" + "".join(
        f"  {label:>{col_w}}" for _, label in metrics
    )

    print("Mean +/- std per compilation method")
    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("Mean % improvement vs SABRE (positive = better than 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):
    """
    Print a per-metric comparison of the compilation methods for the
    first ``num_rows`` circuits (sorted by qubit count). The best
    (lowest) value for each metric is marked with an asterisk.
    """
    metrics = [
        ("two_qubit_depth", "2Q Depth"),
        ("size", "Gate Count"),
        ("runtime", "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} (first {num_rows} circuits by qubit count); * = best")
        header = f"{'Idx':>3}  {'Circuit':<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ゲートを超える回路も(トランスパイレーション時間を妥当な範囲に抑えるため)、削除されます。

# Obtain the Hamiltonian JSON from the benchpress repository
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)

# Remove circuits that are too large for the backend
ham_records = [
    h for h in ham_records if h["ham_qubits"] <= backend.num_qubits
]

# Build PauliEvolutionGate circuits
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)

# Remove circuits whose decomposed size exceeds 1500 gates so that
# transpilation completes in a reasonable time frame
qc_ham_list = [qc for qc in qc_ham_list if qc.decompose().size() <= 1500]

print(f"Total Hamiltonian circuits loaded: {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"Qubit range: {min_qubits} to {max_qubits}")

Output:

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"Small-scale circuits (<20 qubits): {len(qc_small)}")
print(f"Large-scale circuits (>=20 qubits): {len(qc_large)}")

Output:

Small-scale circuits (<20 qubits): 20
Large-scale circuits (>=20 qubits): 22

トランスパイレーションを行う前に、小規模なハミルトニアン回路の1つをプレビューします。

# We decompose the circuit here, otherwise it would just be a PauliEvolutionGate box,
# which isn't very informative to look at!
qc_small[0].decompose().draw("mpl", fold=-1)

Output:

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"
)

Output:

[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
Fetching 4 files:   0%|          | 0/4 [00:00<?, ?it/s]
[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)

Output:

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)

Output:

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):
    """
    Create a three-panel figure comparing compilation methods on
    two-qubit depth, circuit size, and runtime.

    Circuits are sorted by qubit count and plotted by circuit index.
    """
    methods = _method_order(results)
    palette = {"SABRE": "#1f77b4", "AI": "#ff7f0e", "Rustiq": "#2ca02c"}
    markers = {"SABRE": "o", "AI": "^", "Rustiq": "s"}

    # Order circuits by qubit count (then index) and map to plot positions
    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", "Two-Qubit Depth"),
        ("size", "Total Gate Count (Circuit Size)"),
        ("runtime", "Transpilation 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("Circuit Index (num qubits)", 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):
    """
    Plot the per-circuit percent improvement of each non-SABRE method
    relative to SABRE, for each metric. A positive value means the
    method improved on SABRE; negative means SABRE was better.
    """
    metrics = [
        ("two_qubit_depth", "2Q Depth"),
        ("size", "Gate Count"),
        ("runtime", "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}: % Improvement over 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 (baseline)")
        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("Circuit Index (num qubits)", fontsize=11)
        ax.set_ylabel(f"% Improvement ({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,
    "Small-Scale Hamiltonian Circuits: Compilation Comparison",
)

Output:

Output of the previous code cell
plot_pct_improvement_vs_sabre(
    results_small,
    "Small-Scale Hamiltonian Circuits",
)

Output:

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):
    """
    Plot a grouped bar chart showing the percentage of circuits
    where each method achieved the best (lowest) value for each metric.

    Ties are counted for all tied methods, so percentages per metric
    can sum to more than 100%.
    """
    if metrics_list is None:
        metrics_list = ["two_qubit_depth", "size", "runtime"]

    labels = {
        "two_qubit_depth": "2Q Depth",
        "size": "Gate Count",
        "runtime": "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,
                )

    # Annotate tie counts below each metric label
    for j, ties in enumerate(tie_counts):
        if ties > 0:
            ax.text(
                x[j] + width,
                -8,
                f"({ties} tie{'s' if ties != 1 else ''})",
                ha="center",
                va="top",
                fontsize=8,
                color="gray",
            )

    ax.set_xticks(x + width)
    ax.set_xticklabels(metric_labels, fontsize=11)
    ax.set_ylabel("Circuits with best value (%)", fontsize=11)
    ax.set_title(
        "Best-Performing Method by Metric (ties counted for all tied methods)",
        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)

Output:

Output of the previous code cell

この例では、小規模な回路において、これら3つの手法の性能はほぼ同等です。 2量子ビットの深さとゲート数に関して、各手法が最適となる回路の割合は拮抗しており(およそ35~55%)、最も単純な回路では、複数の手法が単一の最適解を導き出すことが多いため、多くの回路で同率となる。 最も明らかな違いは実行時間です。SABREとRustiqは、それぞれ約半数の回路において最速ですが、AIを活用したトランスパイラーが最速となることはほとんどありません。 これら3つの指標を総合的に見ると、Rustiqが全体的にわずかに優位であり、2量子ビットの深度では最も高い勝率を誇り、ゲート数や実行時間においても競争力を維持している。

ステップ3: Qiskit primitivesを使用して実行する

トランスパイレーションの品質がノイズ下での実行にどのような影響を与えるかを評価するために、 ミラー回路の手法を用いる。 トランスパイルされた各回路 UU に対して、その逆回路 UU^\dagger を付加することで、結合された回路 UUU^\dagger U は理論上、恒等回路となる。 0|0\rangle という状態から開始した場合、完全な(ノイズのない)実行では、確率1で全ビットが0のビット列が返される。

実際には、ゲートエラーが回路全体に蓄積されるため、 0n|0\rangle^{\otimes n} を復元できる確率は低下します。 ゲート数が少なく、回路の深さが浅くなるような集積化手法を採用すれば、ノイズの蓄積も少なくなります。

ミラー回路の手法は、期待される出力が常に 0n|0\rangle^{\otimes n} となるため、理想的な状態の古典的なシミュレーションを必要とせず、非常にシンプルで、どのような回路規模にも拡張可能であるという点で魅力的である。 ただし、以下の点に留意してください。ミラー回路は実際の回路の代用であり(回路そのものではありません)、ゲート数が2倍になる(これによりノイズの影響が誇張される)ほか、ミラー境界を挟んでノイズが対称的に打ち消し合う場合、特定のエラーを過小評価してしまう可能性があります。

小規模なセットから回路インデックス6を選び、単純な偏光消去ノイズモデルを用いて、Aerシミュレータ上でミラー回路を実行する。

# Select circuit index 6 from the small-scale transpiled circuits
test_idx = 6
test_circuit = qc_small[test_idx]
print(f"Test circuit: {test_circuit.name}, {test_circuit.num_qubits} qubits")

# Get the transpiled versions
tqc_methods_small = {
    "SABRE": tqc_sabre_small[test_idx],
    "AI": tqc_ai_small[test_idx],
    "Rustiq": tqc_rustiq_small[test_idx],
}

# Show transpilation metrics for this circuit
print(f"\nTranspilation metrics for circuit index {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={depth_2q:5d}  size={size:6d}")

Output:

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

ミラー回路を構築し( UU^\dagger を追加)、シミュレータがアクティブな量子ビットのみを処理するように連続した量子ビットインデックスに再マッピングし、ノイズのある Aer シミュレータ上で実行する。

def remap_to_contiguous(tqc):
    """Remap a transpiled circuit to use contiguous qubit indices.

    Transpiled circuits target specific physical qubits (e.g., qubit 45, 67)
    on a large backend. This remaps them to 0, 1, 2, ... so Aer only
    simulates the active qubits.
    """
    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):
    """Build a mirror circuit: U followed by U-dagger, with measurements.

    The combined circuit U-dagger @ U should be the identity, so measuring
    all zeros indicates a noise-free execution.
    """
    tqc_compact = remap_to_contiguous(tqc)
    mirror = tqc_compact.compose(tqc_compact.inverse())
    mirror.measure_all()
    return mirror


# Build a simple depolarizing noise model
noise_model = NoiseModel()
noise_model.add_all_qubit_quantum_error(
    depolarizing_error(0.001, 1),
    ["sx", "x", "rz"],  # ~0.1% per 1Q gate
)
noise_model.add_all_qubit_quantum_error(
    depolarizing_error(0.01, 2),
    ["cx", "ecr"],  # ~1% per 2Q gate
)

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()

    # Fidelity = fraction of all-zeros (error-free) outcomes
    n_qubits = mirror.num_qubits - mirror.num_clbits  # active qubits
    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})"
    )

Output:

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):
    """
    Plot a three-panel comparison: fidelity, 2Q depth,
    and gate count for each compilation method.
    """
    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"Mirror Circuit Results: {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",
            )

    # Panel 1: Survival Probability
    bars = axes[0].bar(
        methods, fidelity_vals, color=colors, edgecolor="black", linewidth=0.5
    )
    axes[0].set_ylabel("Fidelity  P(|00...0>)", fontsize=11)
    axes[0].set_title("Fidelity (higher is better)", 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}")

    # Panel 2: Two-Qubit Depth
    bars = axes[1].bar(
        methods, depth_vals, color=colors, edgecolor="black", linewidth=0.5
    )
    axes[1].set_ylabel("Two-Qubit Depth", fontsize=11)
    axes[1].set_title("2Q Depth (lower is better)", 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)

    # Panel 3: Gate Count
    bars = axes[2].bar(
        methods, size_vals, color=colors, edgecolor="black", linewidth=0.5
    )
    axes[2].set_ylabel("Total Gate Count", fontsize=11)
    axes[2].set_title("Gate Count (lower is better)", 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)

Output:

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"
)

Output:

[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)

Output:

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)

Output:

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,
    "Large-Scale Hamiltonian Circuits: Compilation Comparison",
)

Output:

Output of the previous code cell
plot_pct_improvement_vs_sabre(
    results_large,
    "Large-Scale Hamiltonian Circuits",
)

Output:

Output of the previous code cell
plot_best_method_bars(results_large)

Output:

Output of the previous code cell
# Select circuit index 3 from the large-scale transpiled circuits
test_idx_large = 3
test_circuit_large = qc_large[test_idx_large]
print(
    f"Test circuit: {test_circuit_large.name}, {test_circuit_large.num_qubits} 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"\nTranspilation metrics for circuit index {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={depth_2q:5d}  size={size:6d}")

Output:

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():
    # print the count ops for each circuit
    mirror = tqc.copy()
    mirror.compose(tqc.inverse(), inplace=True)
    mirror.measure_all()
    mirror = pm_mirror.run(mirror)
    print(f"\n{method} transpiled circuit:")
    print(tqc.count_ops())
    print(f"{method} mirror circuit count ops:")
    print(mirror.count_ops())

Output:


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})
# Build mirror circuits and submit to real hardware
# The inverse may introduce gates (e.g., sxdg) not in the backend's
# basis gate set, so we re-transpile the mirror circuit.
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()

    # Re-transpile at opt level 0 to decompose into basis gates
    # without changing the layout or routing
    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}: submitted job {job.job_id()}")

Output:

SABRE: submitted job d8gvgq66983c73dqe5og
AI: submitted job d8gvgqe6983c73dqe5pg
Rustiq: submitted job d8gvgqm6983c73dqe5q0
# Retrieve results and compute fidelities
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})"
    )

Output:

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
)

Output:

Output of the previous code cell

コンパイル結果の分析

上記のベンチマークでは、AIを活用したトランスパイラーであるSABREとRustiqを、Hamlibコレクションに含まれるハミルトニアンシミュレーション回路を用いて、小規模および大規模の両方で比較しています。

2量子ビットの深さとゲート数

大規模な環境では、SABREとAIを活用したトランスパイラーが最も優れたパフォーマンスを発揮しており、それぞれ異なる評価指標でトップの座を占めています。 メトリックチャートが示すように、SABREは大多数の回路においてゲート数が最も少なく、ほぼすべての回路で最速のアルゴリズムとなっています。これは、挿入されるSWAPゲートを最小限に抑えるよう設計されたヒューリスティック手法や、レイアウトおよび配線に関する最近の最適化と一致しています。 このAI駆動のトランスパイラーは、ほとんどの回路において最も低い2量子ビット深度を実現しており、これは回路の深度を目標とする強化学習の目的と一致している。 要約表にも同様の傾向が表れています。SABREは平均ゲート数が少なく、AIトランスパイラーは平均2量子ビット深度が小さいという結果です。 どちらの方法も、あらゆる種類の回路において一貫性があり、信頼性が高い。

合成専用 PauliEvolutionGate に設計されたRustiqは、大規模な回路のごく一部においてのみ、他を圧倒する最高の結果を生み出しています。 その平均指標は、ごく少数の著しい外れ値によって大きく歪められており、これはコンパイル比較プロット上の大きなスパイクとして確認できる。そこでは、Rustiqが他の手法に比べて、深度およびゲート数が大幅に多い結果を示している。 これらの外れ値がなければ、その平均的なパフォーマンスはSABREやAI搭載のトランスパイラーにかなり近いものとなるだろう。

重要な点は、どの回路においても、特定の1つの手法が他を圧倒しているわけではないということである。 各手法は特定のケースにおいて他の手法よりも優れた性能を発揮するため、利用可能なツールをすべて試して、各回路ごとに最適な結果を選ぶ価値がある。

ランタイム

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の最適化チュートリアルを参照してください)。
  • コンパイル時間が制約とならない場合、特に2量子ビットの深さを最小限に抑えることが優先される場合には、 AIを活用したトランスパイラー を試してみる価値がある。このベンチマークにおける大規模回路のほとんどで、このトランスパイラーが最も低い2量子ビットの深さを実現した。
  • Rustiqは回路設計専用に PauliEvolutionGate 開発されており、特に小規模な回路において、深さが非常に浅く、ゲート数が少ないソリューションを見出すことができます。 大規模な回路では、時折はるかに大きな結果をもたらすことがあるため、デフォルトとしてではなく、試すべきいくつかの手法の一つとして使用するのが最善です。

実際には、利用可能なすべてのメソッドを実行し、各回路について最良の結果を選ぶのが最善のアプローチです。 複数の手法を試すことによるコンパイルのオーバーヘッドは、実際のハードウェア上での実行品質が向上する可能性に比べれば、ごくわずかである。


次のステップ

このチュートリアルが役に立ったという方は、以下の記事もご参考になるかもしれません:


参照

[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] 「強化学習による実用的で効率的な量子回路合成とトランスパイリング 博士 Kremer, V. Villar, H. Paik, I. Duran, I. Faro, J. Cruz-Benito et al. https://arxiv.org/abs/2405.13196

[3] 「強化学習を用いたパウリ・ネットワーク回路合成」。 A。 Dubal, D. Kremer, S. Martiel, V. Villar, D. Wang, J. Cruz-Benito ほか。 https://arxiv.org/abs/2503.14448

[4] 「ハミルトニアンシミュレーション回路のより高速かつ簡略化された合成」。 T. Goubault de Brugiere、S. Martiel ほか https://arxiv.org/abs/2404.03280

このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。