주 콘텐츠로 건너뛰기

해밀토니안 시뮬레이션 Circuit을 위한 컴파일 방법

예상 QPU 사용량: 이 튜토리얼은 트랜스파일 과정에 초점을 맞추고 있으므로 실행은 수행되지 않았습니다.

배경

양자 Circuit 컴파일은 양자 컴퓨팅 워크플로에서 매우 중요한 단계입니다. 이 과정은 고수준의 양자 알고리즘을 대상 양자 하드웨어의 제약 조건을 준수하는 물리적 양자 Circuit으로 변환하는 것을 포함합니다. 효과적인 컴파일은 Circuit 깊이, Gate 수, 실행 시간을 줄임으로써 양자 알고리즘의 성능에 상당한 영향을 미칠 수 있습니다. 이 튜토리얼은 Qiskit에서 양자 Circuit 컴파일의 세 가지 접근 방식을 탐구하며, 실용적인 예제를 통해 각 방법의 강점과 응용을 소개합니다.

이 튜토리얼의 목표는 사용자가 Qiskit에서 세 가지 컴파일 방법, 즉 SABRE Transpiler, AI 기반 Transpiler, Rustiq 플러그인을 적용하고 평가하는 방법을 익히는 것입니다. 사용자는 각 방법을 효과적으로 사용하는 방법과 다양한 양자 Circuit에서 성능을 벤치마크하는 방법을 배웁니다. 튜토리얼이 끝나면, 사용자는 Circuit 깊이 감소, Gate 수 최소화, 런타임 개선 등 특정 최적화 목표에 따라 컴파일 전략을 선택하고 조정할 수 있게 됩니다.

학습 내용

  • 레이아웃 및 라우팅 최적화를 위한 SABRE와 함께 Qiskit Transpiler를 사용하는 방법.
  • 고급 자동화 Circuit 최적화를 위한 AI Transpiler를 활용하는 방법.
  • 해밀토니안 시뮬레이션 작업과 같이 연산의 정밀한 합성이 필요한 Circuit에 Rustiq 플러그인을 적용하는 방법.

이 튜토리얼은 각 컴파일 방법의 성능을 설명하기 위해 Qiskit 패턴 워크플로를 따르는 세 가지 예시 Circuit을 사용합니다. 튜토리얼이 끝나면, 사용자는 특정 요구사항과 제약 조건에 따라 적절한 컴파일 전략을 선택할 수 있게 됩니다.

컴파일 방법 개요

1. SABRE를 사용한 Qiskit Transpiler

Qiskit Transpiler는 SABRE(SWAP 기반 양방향 휴리스틱 탐색) 알고리즘을 사용하여 Circuit 레이아웃 및 라우팅을 최적화합니다. SABRE는 하드웨어 연결 제약을 준수하면서 SWAP Gate와 이로 인한 Circuit 깊이 영향을 최소화하는 데 초점을 맞춥니다. 이 방법은 범용 Circuit 최적화에 매우 유연하게 적용할 수 있으며, 성능과 연산 시간 사이의 균형을 제공합니다. [1]에 자세히 설명된 SABRE의 최신 개선 사항을 활용하려면 시도 횟수를 늘릴 수 있습니다(예: layout_trials=400, swap_trials=400). 이 튜토리얼에서는 Qiskit의 기본 Transpiler와 비교하기 위해 시도 횟수의 기본값을 사용합니다. SABRE의 장점과 파라미터 탐색은 별도의 심화 튜토리얼에서 다룹니다.

2. AI Transpiler

Qiskit의 AI 기반 Transpiler는 머신 러닝을 활용하여 Circuit 구조 및 하드웨어 제약의 패턴을 분석하고, 주어진 입력에 대해 최적의 트랜스파일 전략을 선택합니다. 이 방법은 대규모 양자 Circuit에 특히 효과적이며, 높은 수준의 자동화와 다양한 문제 유형에 대한 적응성을 제공합니다. 일반 Circuit 최적화 외에도, AI Transpiler는 AIPauliNetworkSynthesis 패스와 함께 사용할 수 있습니다. 이 패스는 H, S, SX, CX, RX, RY, RZ Gate로 구성된 블록인 Pauli 네트워크 Circuit을 대상으로 강화 학습 기반의 합성 방식을 적용합니다. AI Transpiler와 합성 전략에 대한 자세한 내용은 [2][3]을 참조하세요.

3. Rustiq 플러그인

Rustiq 플러그인은 트로터화된 동역학에서 일반적으로 사용되는 Pauli 회전을 나타내는 PauliEvolutionGate 연산을 위한 고급 합성 기술을 도입합니다. 이 플러그인은 문제 해밀토니안을 효과적으로 시뮬레이션하기 위해 정확한 Pauli 회전이 필수적인 양자 화학 및 물리학 문제에서 사용되는 해밀토니안 시뮬레이션 Circuit 구현에 유용합니다. Rustiq는 이러한 특수 연산에 대해 정밀하고 낮은 깊이의 Circuit 합성을 제공합니다. Rustiq의 구현 및 성능에 대한 자세한 내용은 [4]를 참조하세요.

이 컴파일 방법들을 심층적으로 탐구함으로써, 이 튜토리얼은 양자 Circuit의 성능을 향상시키는 도구를 사용자에게 제공하고, 보다 효율적이고 실용적인 양자 계산의 길을 열어줍니다.

요구 사항

이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:

  • Qiskit SDK v1.3 이상 (시각화 지원 포함)
  • Qiskit Runtime v0.28 이상 (pip install qiskit-ibm-runtime)
  • Qiskit IBM Transpiler (pip install qiskit-ibm-transpiler)
  • Qiskit AI Transpiler 로컬 모드 (pip install qiskit_ibm_ai_local_transpiler)
  • Networkx 그래프 라이브러리 (pip install networkx)

설정

# Added by doQumentation — required packages for this notebook
!pip install -q IPython matplotlib numpy pandas qiskit qiskit-ibm-runtime qiskit-ibm-transpiler requests
from qiskit.circuit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.circuit.library import (
efficient_su2,
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 collections import Counter
from IPython.display import display
import time
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import json
import requests
import logging

# Suppress noisy loggers
logging.getLogger(
"qiskit_ibm_transpiler.wrappers.ai_local_synthesis"
).setLevel(logging.ERROR)

seed = 42 # Seed for reproducibility

파트 1: Efficient SU2 Circuit

1단계: 고전적 입력을 양자 문제로 매핑하기

이 섹션에서는 변분 양자 알고리즘(VQE 등) 및 양자 머신 러닝 작업에 일반적으로 사용되는 하드웨어 효율적인 앤사츠인 efficient_su2 Circuit을 탐구합니다. 이 Circuit은 단일 Qubit 회전 레이어와 원형 패턴으로 배열된 얽힘 Gate의 교대 레이어로 구성되어 있으며, 관리 가능한 깊이를 유지하면서 양자 상태 공간을 효과적으로 탐색할 수 있도록 설계되었습니다.

우선 하나의 efficient_su2 Circuit을 구성하여 다양한 컴파일 방법을 비교하는 방법을 시연합니다. 파트 1 이후에는 더 많은 Circuit 집합으로 분석을 확장하여 다양한 컴파일 기법의 성능을 평가하는 포괄적인 벤치마크를 가능하게 합니다.

qubit_size = list(range(10, 101, 10))
qc_su2_list = [
efficient_su2(n, entanglement="circular", reps=1)
.decompose()
.copy(name=f"SU2_{n}")
for n in qubit_size
]

# Draw the first circuit
qc_su2_list[0].draw(output="mpl")

이전 코드 셀의 출력

Step 2: 양자 하드웨어 실행을 위한 문제 최적화

이 단계는 튜토리얼의 핵심입니다. 여기서는 실제 양자 하드웨어에서 효율적으로 실행할 수 있도록 양자 Circuit을 최적화하는 것이 목표입니다. 주요 목적은 Circuit 깊이와 Gate 수를 줄이는 것이며, 이는 실행 충실도를 높이고 하드웨어 노이즈를 완화하는 핵심 요소입니다.

  • SABRE Transpiler: Qiskit의 기본 Transpiler와 SABRE 레이아웃 및 라우팅 알고리즘을 사용합니다.
  • AI Transpiler (로컬 모드): 로컬 추론과 기본 합성 전략을 사용하는 표준 AI 기반 Transpiler입니다.
  • Rustiq 플러그인: 해밀토니안 시뮬레이션 작업에 맞춘 낮은 깊이 컴파일을 위해 설계된 Transpiler 플러그인입니다.

이 단계의 목표는 트랜스파일된 Circuit의 깊이와 Gate 수 측면에서 이러한 방법들의 결과를 비교하는 것입니다. 또한 중요하게 고려하는 지표로 트랜스파일 런타임이 있습니다. 이러한 지표들을 분석함으로써 각 방법의 상대적 강점을 평가하고, 선택된 하드웨어에서 실행하기에 가장 효율적인 Circuit을 생성하는 방법을 결정할 수 있습니다.

참고: 초기 SU2 Circuit 예제에서는 SABRE Transpiler와 기본 AI Transpiler만 비교합니다. 그러나 이후 Hamlib Circuit을 사용하는 벤치마크에서는 세 가지 트랜스파일 방법 모두를 비교합니다.

# 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.backend("ibm_torino")
print(f"Using backend: {backend}")
qiskit_runtime_service._get_crn_from_instance_name:WARNING:2025-07-30 21:46:30,843: Multiple instances found. Using all matching instances.
Using backend: <IBMBackend('ibm_torino')>

SABRE를 사용하는 Qiskit Transpiler:

pm_sabre = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)

AI Transpiler:

# Standard AI transpiler pass manager, using the local mode
pm_ai = generate_ai_pass_manager(
backend=backend, optimization_level=3, ai_optimization_level=3
)

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

트랜스파일 및 지표 수집

컴파일 방법들의 성능을 비교하기 위해, 입력 Circuit을 트랜스파일하고 관련 지표를 일관된 방식으로 수집하는 함수를 정의합니다. 여기에는 전체 Circuit 깊이, 전체 Gate 수, 트랜스파일 시간이 포함됩니다.

이러한 표준 지표 외에도, 2-Qubit Gate 깊이도 함께 기록합니다. 이는 양자 하드웨어에서의 실행을 평가하는 데 특히 중요한 지표입니다. 모든 Gate를 포함하는 전체 깊이와 달리, 2-Qubit 깊이는 하드웨어에서의 실제 실행 시간을 보다 정확하게 반영합니다. 이는 대부분의 양자 디바이스에서 2-Qubit Gate가 시간과 오류 예산의 대부분을 차지하기 때문입니다. 따라서 2-Qubit 깊이를 최소화하는 것은 실행 충실도를 높이고 실행 중 결어긋남 효과를 줄이는 데 매우 중요합니다.

이 함수를 사용하여 여러 Circuit에 걸쳐 다양한 컴파일 방법의 성능을 분석할 것입니다.

def capture_transpilation_metrics(
results, pass_manager, circuits, method_name
):
"""
Capture transpilation metrics for a list of circuits and stores the results in a DataFrame.

Args:
results (pd.DataFrame): DataFrame to store the results.
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):
# Transpile the circuit
start_time = time.time()
transpiled_qc = pass_manager.run(qc)
end_time = time.time()

# Needed for AI transpiler to be consistent with other methods
transpiled_qc = transpiled_qc.decompose(gates_to_decompose=["swap"])

# Collect metrics
transpilation_time = end_time - start_time
circuit_depth = transpiled_qc.depth(
lambda x: x.operation.num_qubits == 2
)
circuit_size = transpiled_qc.size()

# Append results to DataFrame
results.loc[len(results)] = {
"method": method_name,
"qc_name": qc.name,
"qc_index": i,
"num_qubits": qc.num_qubits,
"ops": transpiled_qc.count_ops(),
"depth": circuit_depth,
"size": circuit_size,
"runtime": transpilation_time,
}
transpiled_circuits.append(transpiled_qc)
print(
f"Transpiled circuit index {i} ({qc.name}) in {transpilation_time:.2f} seconds with method {method_name}, "
f"depth {circuit_depth}, and size {circuit_size}."
)

return transpiled_circuits
results_su2 = pd.DataFrame(
columns=[
"method",
"qc_name",
"qc_index",
"num_qubits",
"ops",
"depth",
"size",
"runtime",
]
)

tqc_sabre = capture_transpilation_metrics(
results_su2, pm_sabre, qc_su2_list, "sabre"
)
tqc_ai = capture_transpilation_metrics(results_su2, pm_ai, qc_su2_list, "ai")
Transpiled circuit index 0 (SU2_10) in 0.06 seconds with method sabre, depth 13, and size 167.
Transpiled circuit index 1 (SU2_20) in 0.24 seconds with method sabre, depth 20, and size 299.
Transpiled circuit index 2 (SU2_30) in 10.72 seconds with method sabre, depth 72, and size 627.
Transpiled circuit index 3 (SU2_40) in 16.16 seconds with method sabre, depth 40, and size 599.
Transpiled circuit index 4 (SU2_50) in 76.89 seconds with method sabre, depth 77, and size 855.
Transpiled circuit index 5 (SU2_60) in 86.12 seconds with method sabre, depth 60, and size 899.
Transpiled circuit index 6 (SU2_70) in 94.46 seconds with method sabre, depth 79, and size 1085.
Transpiled circuit index 7 (SU2_80) in 69.05 seconds with method sabre, depth 80, and size 1199.
Transpiled circuit index 8 (SU2_90) in 88.25 seconds with method sabre, depth 105, and size 1420.
Transpiled circuit index 9 (SU2_100) in 83.80 seconds with method sabre, depth 100, and size 1499.
Transpiled circuit index 0 (SU2_10) in 0.17 seconds with method ai, depth 10, and size 168.
Transpiled circuit index 1 (SU2_20) in 0.29 seconds with method ai, depth 20, and size 299.
Transpiled circuit index 2 (SU2_30) in 13.56 seconds with method ai, depth 36, and size 548.
Transpiled circuit index 3 (SU2_40) in 15.95 seconds with method ai, depth 40, and size 599.
Transpiled circuit index 4 (SU2_50) in 80.70 seconds with method ai, depth 54, and size 823.
Transpiled circuit index 5 (SU2_60) in 75.99 seconds with method ai, depth 60, and size 899.
Transpiled circuit index 6 (SU2_70) in 64.96 seconds with method ai, depth 74, and size 1087.
Transpiled circuit index 7 (SU2_80) in 68.25 seconds with method ai, depth 80, and size 1199.
Transpiled circuit index 8 (SU2_90) in 75.07 seconds with method ai, depth 90, and size 1404.
Transpiled circuit index 9 (SU2_100) in 63.97 seconds with method ai, depth 100, and size 1499.

Circuit 중 하나의 트랜스파일 결과를 표시합니다.

print("Sabre transpilation")
display(tqc_sabre[0].draw("mpl", fold=-1, idle_wires=False))
print("AI transpilation")
display(tqc_ai[0].draw("mpl", fold=-1, idle_wires=False))
Sabre transpilation

이전 코드 셀의 출력

AI transpilation

이전 코드 셀의 출력

결과 표:

summary_su2 = (
results_su2.groupby("method")[["depth", "size", "runtime"]]
.mean()
.round(2)
)
print(summary_su2)

results_su2
depth   size  runtime
method
ai 56.4 852.5 45.89
sabre 64.6 864.9 52.57
method  qc_name  qc_index  num_qubits                                ops  \
0 sabre SU2_10 0 10 {'rz': 81, 'sx': 70, 'cz': 16}
1 sabre SU2_20 1 20 {'rz': 160, 'sx': 119, 'cz': 20}
2 sabre SU2_30 2 30 {'sx': 295, 'rz': 242, 'cz': 90}
3 sabre SU2_40 3 40 {'rz': 320, 'sx': 239, 'cz': 40}
4 sabre SU2_50 4 50 {'rz': 402, 'sx': 367, 'cz': 86}
5 sabre SU2_60 5 60 {'rz': 480, 'sx': 359, 'cz': 60}
6 sabre SU2_70 6 70 {'rz': 562, 'sx': 441, 'cz': 82}
7 sabre SU2_80 7 80 {'rz': 640, 'sx': 479, 'cz': 80}
8 sabre SU2_90 8 90 {'rz': 721, 'sx': 585, 'cz': 114}
9 sabre SU2_100 9 100 {'rz': 800, 'sx': 599, 'cz': 100}
10 ai SU2_10 0 10 {'rz': 81, 'sx': 71, 'cz': 16}
11 ai SU2_20 1 20 {'rz': 160, 'sx': 119, 'cz': 20}
12 ai SU2_30 2 30 {'sx': 243, 'rz': 242, 'cz': 63}
13 ai SU2_40 3 40 {'rz': 320, 'sx': 239, 'cz': 40}
14 ai SU2_50 4 50 {'rz': 403, 'sx': 346, 'cz': 74}
15 ai SU2_60 5 60 {'rz': 480, 'sx': 359, 'cz': 60}
16 ai SU2_70 6 70 {'rz': 563, 'sx': 442, 'cz': 82}
17 ai SU2_80 7 80 {'rz': 640, 'sx': 479, 'cz': 80}
18 ai SU2_90 8 90 {'rz': 721, 'sx': 575, 'cz': 108}
19 ai SU2_100 9 100 {'rz': 800, 'sx': 599, 'cz': 100}

depth size runtime
0 13 167 0.058845
1 20 299 0.238217
2 72 627 10.723922
3 40 599 16.159262
4 77 855 76.886604
5 60 899 86.118255
6 79 1085 94.458287
7 80 1199 69.048184
8 105 1420 88.254809
9 100 1499 83.795482
10 10 168 0.171532
11 20 299 0.291691
12 36 548 13.555931
13 40 599 15.952733
14 54 823 80.702141
15 60 899 75.993404
16 74 1087 64.960162
17 80 1199 68.253280
18 90 1404 75.072412
19 100 1499 63.967446

결과 그래프

지표를 일관되게 수집하는 함수를 정의한 것처럼, 지표를 시각화하는 함수도 정의합니다. 여기서는 각 컴파일 방법별로 회로 전반에 걸친 2-큐비트 깊이, 게이트 수, 런타임을 도식화합니다.

def plot_transpilation_metrics(results, overall_title, x_axis="qc_index"):
"""
Plots transpilation metrics (depth, size, runtime) for different transpilation methods.

Parameters:
results (DataFrame): Data containing columns ['num_qubits', 'method', 'depth', 'size', 'runtime']
overall_title (str): The title of the overall figure.
x_axis (str): The x-axis label, either 'num_qubits' or 'qc_index'.
"""

fig, axs = plt.subplots(1, 3, figsize=(24, 6))
metrics = ["depth", "size", "runtime"]
titles = ["Circuit Depth", "Circuit Size", "Transpilation Runtime"]
y_labels = ["Depth", "Size (Gate Count)", "Runtime (s)"]

methods = results["method"].unique()
colors = plt.colormaps["tab10"]
markers = ["o", "^", "s", "D", "P", "*", "X", "v"]
color_list = [colors(i % colors.N) for i in range(len(methods))]
color_map = {method: color_list[i] for i, method in enumerate(methods)}
marker_map = {
method: markers[i % len(markers)] for i, method in enumerate(methods)
}
jitter_factor = 0.1 # Small x-axis jitter for visibility
handles, labels = [], [] # Unique handles for legend

# Plot each metric
for i, metric in enumerate(metrics):
for method in methods:
method_data = results[results["method"] == method]

# Introduce slight jitter to avoid exact overlap
jitter = np.random.uniform(
-jitter_factor, jitter_factor, len(method_data)
)

scatter = axs[i].scatter(
method_data[x_axis] + jitter,
method_data[metric],
color=color_map[method],
label=method,
marker=marker_map[method],
alpha=0.7,
edgecolors="black",
s=80,
)

if method not in labels:
handles.append(scatter)
labels.append(method)

axs[i].set_title(titles[i])
axs[i].set_xlabel(x_axis)
axs[i].set_ylabel(y_labels[i])
axs[i].grid(axis="y", linestyle="--", alpha=0.7)
axs[i].tick_params(axis="x", rotation=45)
axs[i].set_xticks(sorted(results[x_axis].unique()))

fig.suptitle(overall_title, fontsize=16)
fig.legend(
handles=handles,
labels=labels,
loc="upper right",
bbox_to_anchor=(1.05, 1),
)

plt.tight_layout()
plt.show()
plot_transpilation_metrics(
results_su2, "Transpilation Metrics for SU2 Circuits", x_axis="num_qubits"
)

이전 코드 셀의 출력

SU2 회로 컴파일 결과 분석

이 실험에서는 efficient_su2 회로 집합을 대상으로 Qiskit의 SABRE Transpiler와 AI 기반 Transpiler, 두 가지 트랜스파일 방법을 비교합니다. 이 회로들은 PauliEvolutionGate 연산을 포함하지 않으므로 Rustiq 플러그인은 이 비교에서 제외됩니다.

평균적으로 AI Transpiler는 전체 SU2 회로 범위에 걸쳐 10% 이상의 개선을 보이며 회로 깊이 측면에서 더 나은 성능을 발휘합니다. 게이트 수(회로 크기)와 트랜스파일 런타임은 두 방법 모두 전반적으로 비슷한 결과를 보입니다.

그러나 개별 데이터 포인트를 살펴보면 더 깊은 통찰을 얻을 수 있습니다.

  • 대부분의 큐비트 크기에서 SABRE와 AI 모두 거의 동일한 결과를 생성하며, 이는 많은 경우 두 방법이 유사하게 효율적인 해법으로 수렴함을 시사합니다.
  • 특정 회로 크기, 구체적으로 30, 50, 70, 90 큐비트에서 AI Transpiler가 SABRE보다 훨씬 얕은 회로를 찾아냅니다. 이는 SABRE 휴리스틱이 최적 해를 찾지 못하는 경우에 AI의 학습 기반 접근 방식이 더 최적화된 레이아웃이나 라우팅 경로를 발견할 수 있음을 보여줍니다.

이러한 동작은 중요한 시사점을 강조합니다:

SABRE와 AI가 종종 비슷한 결과를 낼 때도 있지만, AI Transpiler는 특히 깊이 측면에서 훨씬 더 나은 해법을 발견할 수 있으며, 이는 하드웨어에서의 성능을 크게 향상시킬 수 있습니다.

파트 2: 해밀토니안 시뮬레이션 회로

단계 1: PauliEvolutionGate를 사용한 회로 조사

이 섹션에서는 PauliEvolutionGate를 사용하여 구성된 양자 회로를 조사합니다. 이 게이트는 해밀토니안의 효율적인 시뮬레이션을 가능하게 합니다. 다양한 해밀토니안에 걸쳐 서로 다른 컴파일 방법이 이러한 회로를 어떻게 최적화하는지 분석합니다.

벤치마크에 사용된 해밀토니안

이 벤치마크에 사용된 해밀토니안은 ZZZZ, XXXX, YYYY 등의 항을 포함하여 큐비트 간의 쌍별 상호작용을 기술합니다. 이러한 해밀토니안은 상호작용하는 입자 시스템을 모델링하는 양자 화학, 응집물질 물리학, 재료 과학에서 일반적으로 사용됩니다.

참고로, 사용자는 이 논문에서 더 넓은 해밀토니안 집합을 탐색할 수 있습니다: Efficient Hamiltonian Simulation on Noisy Quantum Devices.

벤치마크 출처: Hamlib 및 Benchpress

이 벤치마크에 사용된 회로는 현실적인 해밀토니안 시뮬레이션 워크로드를 포함하는 Hamlib 벤치마크 저장소에서 가져왔습니다.

이 회로들은 이전에 양자 트랜스파일 성능을 평가하기 위한 오픈소스 프레임워크인 Benchpress를 사용하여 벤치마크된 바 있습니다. 이 표준화된 회로 집합을 사용함으로써 대표적인 시뮬레이션 문제에 대한 서로 다른 컴파일 전략의 효과를 직접 비교할 수 있습니다.

해밀토니안 시뮬레이션은 분자 시뮬레이션, 최적화 문제, 양자 다체 물리학 등에 응용되는 양자 컴퓨팅의 핵심 과제입니다. 서로 다른 컴파일 방법이 이러한 회로를 어떻게 최적화하는지 이해하면 근거리 양자 디바이스에서 이러한 회로를 실질적으로 실행하는 데 도움이 됩니다.

# 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() # Raise an error if download failed
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
]
# Remove the circuits that are large to save transpilation time
ham_records = sorted(ham_records, key=lambda x: x["ham_terms"])[:35]

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_ham = QuantumCircuit(num_qubits)
qc_ham.name = name

qc_ham.append(evo_gate, range(num_qubits))
qc_ham_list.append(qc_ham)
print(f"Number of Hamiltonian circuits: {len(qc_ham_list)}")

# Draw the first Hamiltonian circuit
qc_ham_list[0].draw("mpl", fold=-1)
Number of Hamiltonian circuits: 35

이전 코드 셀의 출력

Step 2: Optimize problem for quantum hardware execution

이전 예제와 마찬가지로, 비교의 일관성을 유지하기 위해 동일한 Backend를 사용합니다. Pass Manager(pm_sabre, pm_ai, pm_rustiq)는 이미 초기화되어 있으므로, 각 방법을 사용하여 해밀토니안 Circuit을 곧바로 트랜스파일할 수 있습니다.

이 단계는 트랜스파일을 수행하고 깊이(depth), Gate 수, 트랜스파일 실행 시간 등 결과 Circuit 지표를 기록하는 것에 집중합니다. 이 결과를 분석하여 이 유형의 Circuit에 대한 각 트랜스파일 방법의 효율성을 파악합니다. 트랜스파일 후 지표를 캡처합니다:

results_ham = pd.DataFrame(
columns=[
"method",
"qc_name",
"qc_index",
"num_qubits",
"ops",
"depth",
"size",
"runtime",
]
)

tqc_sabre = capture_transpilation_metrics(
results_ham, pm_sabre, qc_ham_list, "sabre"
)
tqc_ai = capture_transpilation_metrics(results_ham, pm_ai, qc_ham_list, "ai")
tqc_rustiq = capture_transpilation_metrics(
results_ham, pm_rustiq, qc_ham_list, "rustiq"
)
Transpiled circuit index 0 (all-vib-o3) in 0.02 seconds with method sabre, depth 6, and size 58.
Transpiled circuit index 1 (all-vib-c2h) in 1.10 seconds with method sabre, depth 2, and size 39.
Transpiled circuit index 2 (all-vib-bh) in 0.01 seconds with method sabre, depth 3, and size 30.
Transpiled circuit index 3 (all-vib-c2h) in 0.03 seconds with method sabre, depth 18, and size 115.
Transpiled circuit index 4 (graph-gnp_k-2) in 0.02 seconds with method sabre, depth 24, and size 129.
Transpiled circuit index 5 (all-vib-fccf) in 0.05 seconds with method sabre, depth 14, and size 134.
Transpiled circuit index 6 (all-vib-hno) in 8.39 seconds with method sabre, depth 6, and size 174.
Transpiled circuit index 7 (all-vib-bhf2) in 3.92 seconds with method sabre, depth 22, and size 220.
Transpiled circuit index 8 (LiH) in 0.03 seconds with method sabre, depth 67, and size 290.
Transpiled circuit index 9 (uf20-ham) in 0.04 seconds with method sabre, depth 50, and size 340.
Transpiled circuit index 10 (all-vib-fccf) in 0.62 seconds with method sabre, depth 30, and size 286.
Transpiled circuit index 11 (all-vib-fccf) in 0.04 seconds with method sabre, depth 67, and size 339.
Transpiled circuit index 12 (all-vib-ch2) in 0.04 seconds with method sabre, depth 87, and size 421.
Transpiled circuit index 13 (tfim) in 0.05 seconds with method sabre, depth 36, and size 222.
Transpiled circuit index 14 (all-vib-cyclo_propene) in 9.51 seconds with method sabre, depth 22, and size 345.
Transpiled circuit index 15 (graph-gnp_k-4) in 0.05 seconds with method sabre, depth 128, and size 704.
Transpiled circuit index 16 (all-vib-hc3h2cn) in 13.83 seconds with method sabre, depth 2, and size 242.
Transpiled circuit index 17 (TSP_Ncity-4) in 0.05 seconds with method sabre, depth 106, and size 609.
Transpiled circuit index 18 (tfim) in 0.29 seconds with method sabre, depth 73, and size 399.
Transpiled circuit index 19 (all-vib-h2co) in 21.97 seconds with method sabre, depth 30, and size 572.
Transpiled circuit index 20 (Be2) in 0.09 seconds with method sabre, depth 324, and size 1555.
Transpiled circuit index 21 (graph-complete_bipart) in 0.12 seconds with method sabre, depth 250, and size 1394.
Transpiled circuit index 22 (all-vib-f2) in 0.07 seconds with method sabre, depth 215, and size 1027.
Transpiled circuit index 23 (all-vib-cyclo_propene) in 41.22 seconds with method sabre, depth 30, and size 1144.
Transpiled circuit index 24 (TSP_Ncity-5) in 1.89 seconds with method sabre, depth 175, and size 1933.
Transpiled circuit index 25 (H2) in 0.32 seconds with method sabre, depth 1237, and size 5502.
Transpiled circuit index 26 (uuf100-ham) in 0.20 seconds with method sabre, depth 385, and size 4303.
Transpiled circuit index 27 (ham-graph-gnp_k-5) in 0.20 seconds with method sabre, depth 311, and size 3654.
Transpiled circuit index 28 (tfim) in 0.15 seconds with method sabre, depth 276, and size 3213.
Transpiled circuit index 29 (uuf100-ham) in 0.21 seconds with method sabre, depth 520, and size 5250.
Transpiled circuit index 30 (flat100-ham) in 0.15 seconds with method sabre, depth 131, and size 3157.
Transpiled circuit index 31 (uf100-ham) in 0.24 seconds with method sabre, depth 624, and size 7378.
Transpiled circuit index 32 (OH) in 0.88 seconds with method sabre, depth 2175, and size 9808.
Transpiled circuit index 33 (HF) in 0.66 seconds with method sabre, depth 2206, and size 9417.
Transpiled circuit index 34 (BH) in 0.89 seconds with method sabre, depth 2177, and size 9802.
Transpiled circuit index 0 (all-vib-o3) in 0.02 seconds with method ai, depth 6, and size 58.
Transpiled circuit index 1 (all-vib-c2h) in 1.11 seconds with method ai, depth 2, and size 39.
Transpiled circuit index 2 (all-vib-bh) in 0.01 seconds with method ai, depth 3, and size 30.
Transpiled circuit index 3 (all-vib-c2h) in 0.11 seconds with method ai, depth 18, and size 94.
Transpiled circuit index 4 (graph-gnp_k-2) in 0.11 seconds with method ai, depth 22, and size 129.
Transpiled circuit index 5 (all-vib-fccf) in 0.06 seconds with method ai, depth 22, and size 177.
Transpiled circuit index 6 (all-vib-hno) in 8.62 seconds with method ai, depth 10, and size 198.
Transpiled circuit index 7 (all-vib-bhf2) in 3.71 seconds with method ai, depth 18, and size 195.
Transpiled circuit index 8 (LiH) in 0.19 seconds with method ai, depth 62, and size 267.
Transpiled circuit index 9 (uf20-ham) in 0.22 seconds with method ai, depth 47, and size 321.
Transpiled circuit index 10 (all-vib-fccf) in 0.71 seconds with method ai, depth 38, and size 369.
Transpiled circuit index 11 (all-vib-fccf) in 0.24 seconds with method ai, depth 65, and size 315.
Transpiled circuit index 12 (all-vib-ch2) in 0.24 seconds with method ai, depth 91, and size 430.
Transpiled circuit index 13 (tfim) in 0.15 seconds with method ai, depth 12, and size 251.
Transpiled circuit index 14 (all-vib-cyclo_propene) in 8.50 seconds with method ai, depth 18, and size 311.
Transpiled circuit index 15 (graph-gnp_k-4) in 0.25 seconds with method ai, depth 117, and size 659.
Transpiled circuit index 16 (all-vib-hc3h2cn) in 16.11 seconds with method ai, depth 2, and size 242.
Transpiled circuit index 17 (TSP_Ncity-4) in 0.39 seconds with method ai, depth 98, and size 564.
Transpiled circuit index 18 (tfim) in 0.38 seconds with method ai, depth 23, and size 437.
Transpiled circuit index 19 (all-vib-h2co) in 24.97 seconds with method ai, depth 38, and size 707.
Transpiled circuit index 20 (Be2) in 1.07 seconds with method ai, depth 293, and size 1392.
Transpiled circuit index 21 (graph-complete_bipart) in 0.61 seconds with method ai, depth 229, and size 1437.
Transpiled circuit index 22 (all-vib-f2) in 0.57 seconds with method ai, depth 178, and size 964.
Transpiled circuit index 23 (all-vib-cyclo_propene) in 50.89 seconds with method ai, depth 34, and size 1425.
Transpiled circuit index 24 (TSP_Ncity-5) in 1.61 seconds with method ai, depth 171, and size 2020.
Transpiled circuit index 25 (H2) in 6.39 seconds with method ai, depth 1148, and size 5208.
Transpiled circuit index 26 (uuf100-ham) in 3.97 seconds with method ai, depth 376, and size 5048.
Transpiled circuit index 27 (ham-graph-gnp_k-5) in 3.54 seconds with method ai, depth 357, and size 4451.
Transpiled circuit index 28 (tfim) in 1.72 seconds with method ai, depth 216, and size 3026.
Transpiled circuit index 29 (uuf100-ham) in 4.45 seconds with method ai, depth 426, and size 5399.
Transpiled circuit index 30 (flat100-ham) in 7.02 seconds with method ai, depth 86, and size 3108.
Transpiled circuit index 31 (uf100-ham) in 12.85 seconds with method ai, depth 623, and size 8354.
Transpiled circuit index 32 (OH) in 15.19 seconds with method ai, depth 2084, and size 9543.
Transpiled circuit index 33 (HF) in 17.51 seconds with method ai, depth 2063, and size 9446.
Transpiled circuit index 34 (BH) in 15.33 seconds with method ai, depth 2094, and size 9730.
Transpiled circuit index 0 (all-vib-o3) in 0.02 seconds with method rustiq, depth 13, and size 83.
Transpiled circuit index 1 (all-vib-c2h) in 1.11 seconds with method rustiq, depth 2, and size 39.
Transpiled circuit index 2 (all-vib-bh) in 0.01 seconds with method rustiq, depth 3, and size 30.
Transpiled circuit index 3 (all-vib-c2h) in 0.01 seconds with method rustiq, depth 13, and size 79.
Transpiled circuit index 4 (graph-gnp_k-2) in 0.02 seconds with method rustiq, depth 31, and size 131.
Transpiled circuit index 5 (all-vib-fccf) in 0.04 seconds with method rustiq, depth 50, and size 306.
Transpiled circuit index 6 (all-vib-hno) in 14.03 seconds with method rustiq, depth 22, and size 276.
Transpiled circuit index 7 (all-vib-bhf2) in 3.15 seconds with method rustiq, depth 13, and size 155.
Transpiled circuit index 8 (LiH) in 0.03 seconds with method rustiq, depth 54, and size 270.
Transpiled circuit index 9 (uf20-ham) in 0.04 seconds with method rustiq, depth 65, and size 398.
Transpiled circuit index 10 (all-vib-fccf) in 0.16 seconds with method rustiq, depth 41, and size 516.
Transpiled circuit index 11 (all-vib-fccf) in 0.02 seconds with method rustiq, depth 34, and size 189.
Transpiled circuit index 12 (all-vib-ch2) in 0.03 seconds with method rustiq, depth 49, and size 240.
Transpiled circuit index 13 (tfim) in 0.05 seconds with method rustiq, depth 20, and size 366.
Transpiled circuit index 14 (all-vib-cyclo_propene) in 9.08 seconds with method rustiq, depth 16, and size 277.
Transpiled circuit index 15 (graph-gnp_k-4) in 0.04 seconds with method rustiq, depth 116, and size 612.
Transpiled circuit index 16 (all-vib-hc3h2cn) in 13.89 seconds with method rustiq, depth 2, and size 257.
Transpiled circuit index 17 (TSP_Ncity-4) in 0.05 seconds with method rustiq, depth 133, and size 737.
Transpiled circuit index 18 (tfim) in 0.11 seconds with method rustiq, depth 25, and size 680.
Transpiled circuit index 19 (all-vib-h2co) in 27.19 seconds with method rustiq, depth 66, and size 983.
Transpiled circuit index 20 (Be2) in 0.07 seconds with method rustiq, depth 215, and size 1030.
Transpiled circuit index 21 (graph-complete_bipart) in 0.14 seconds with method rustiq, depth 328, and size 1918.
Transpiled circuit index 22 (all-vib-f2) in 0.05 seconds with method rustiq, depth 114, and size 692.
Transpiled circuit index 23 (all-vib-cyclo_propene) in 62.25 seconds with method rustiq, depth 74, and size 2348.
Transpiled circuit index 24 (TSP_Ncity-5) in 0.20 seconds with method rustiq, depth 436, and size 3605.
Transpiled circuit index 25 (H2) in 0.21 seconds with method rustiq, depth 643, and size 3476.
Transpiled circuit index 26 (uuf100-ham) in 0.24 seconds with method rustiq, depth 678, and size 6120.
Transpiled circuit index 27 (ham-graph-gnp_k-5) in 0.22 seconds with method rustiq, depth 588, and size 5241.
Transpiled circuit index 28 (tfim) in 0.34 seconds with method rustiq, depth 340, and size 5901.
Transpiled circuit index 29 (uuf100-ham) in 0.33 seconds with method rustiq, depth 881, and size 7667.
Transpiled circuit index 30 (flat100-ham) in 0.31 seconds with method rustiq, depth 279, and size 4910.
Transpiled circuit index 31 (uf100-ham) in 0.38 seconds with method rustiq, depth 1138, and size 10607.
Transpiled circuit index 32 (OH) in 0.38 seconds with method rustiq, depth 1148, and size 6512.
Transpiled circuit index 33 (HF) in 0.37 seconds with method rustiq, depth 1090, and size 6256.
Transpiled circuit index 34 (BH) in 0.37 seconds with method rustiq, depth 1148, and size 6501.

결과 표 (출력 Circuit이 매우 크므로 시각화는 생략합니다):

summary_ham = (
results_ham.groupby("method")[["depth", "size", "runtime"]]
.mean()
.round(2)
)
print(summary_ham)

results_ham
depth     size  runtime
method
ai 316.86 2181.26 5.97
rustiq 281.94 2268.80 3.86
sabre 337.97 2120.14 3.07
method        qc_name  qc_index  num_qubits  \
0 sabre all-vib-o3 0 4
1 sabre all-vib-c2h 1 4
2 sabre all-vib-bh 2 2
3 sabre all-vib-c2h 3 3
4 sabre graph-gnp_k-2 4 4
.. ... ... ... ...
100 rustiq flat100-ham 30 90
101 rustiq uf100-ham 31 46
102 rustiq OH 32 10
103 rustiq HF 33 10
104 rustiq BH 34 10

ops depth size runtime
0 {'rz': 28, 'sx': 24, 'cz': 6} 6 58 0.016597
1 {'rz': 17, 'sx': 16, 'cz': 4, 'x': 2} 2 39 1.102089
2 {'sx': 14, 'rz': 13, 'cz': 3} 3 30 0.011042
3 {'sx': 46, 'rz': 45, 'cz': 18, 'x': 6} 18 115 0.025816
4 {'sx': 49, 'rz': 47, 'cz': 24, 'x': 9} 24 129 0.023077
.. ... ... ... ...
100 {'sx': 2709, 'cz': 1379, 'rz': 817, 'x': 5} 279 4910 0.309448
101 {'sx': 6180, 'cz': 3120, 'rz': 1303, 'x': 4} 1138 10607 0.380977
102 {'sx': 3330, 'cz': 1704, 'rz': 1455, 'x': 23} 1148 6512 0.383564
103 {'sx': 3213, 'cz': 1620, 'rz': 1406, 'x': 17} 1090 6256 0.368578
104 {'sx': 3331, 'cz': 1704, 'rz': 1447, 'x': 19} 1148 6501 0.374822

[105 rows x 8 columns]

Circuit 인덱스를 기준으로 성능을 시각화합니다:

plot_transpilation_metrics(
results_ham, "Transpilation Metrics for Hamiltonian Circuits"
)

Output of the previous code cell

각 방법이 가장 좋은 성능을 보인 Circuit의 비율을 시각화합니다.

def analyze_and_plot_best_methods(results, metric):
"""
Analyze the best-performing methods for a given metric and plot a pie chart.

Parameters:
results (DataFrame): The input DataFrame containing method performance data.
metric (str): The metric to evaluate ("depth" or "size").
"""
method_counts = Counter()
for qc_idx, group in results.groupby("qc_index"):
min_value = group[metric].min()

# Find all methods that achieved this minimum value
best_methods = group[group[metric] == min_value]["method"]
# Update counts for all best methods (handling ties)
method_counts.update(best_methods)
best_method_counts = dict(
sorted(method_counts.items(), key=lambda x: x[1], reverse=True)
)

# Print summary
print(f"Best-performing methods based on {metric}:")
for method, count in best_method_counts.items():
print(f" {method}: {count} circuit(s)")

# Plot pie chart
num_methods = len(best_method_counts)
colors = plt.cm.viridis_r(range(0, 256, 256 // num_methods))
plt.figure(figsize=(5, 5))
plt.pie(
best_method_counts.values(),
labels=best_method_counts.keys(),
autopct="%1.1f%%",
startangle=140,
wedgeprops={"edgecolor": "black"},
textprops={"fontsize": 10},
colors=colors,
)
plt.title(
f"Percentage of Circuits Method Performed Best for {metric.capitalize()}",
fontsize=12,
fontweight="bold",
)
plt.show()

analyze_and_plot_best_methods(results_ham, "depth")
analyze_and_plot_best_methods(results_ham, "size")
Best-performing methods based on depth:
ai: 16 circuit(s)
rustiq: 16 circuit(s)
sabre: 10 circuit(s)

Output of the previous code cell

Best-performing methods based on size:
sabre: 18 circuit(s)
rustiq: 14 circuit(s)
ai: 10 circuit(s)

Output of the previous code cell

해밀토니안 Circuit 컴파일 결과 분석

이 섹션에서는 해밀토니안 시뮬레이션 작업에서 일반적으로 사용되는 PauliEvolutionGate로 구성된 양자 Circuit에 대해 세 가지 트랜스파일 방법(SABRE, AI 기반 Transpiler, Rustiq)의 성능을 평가합니다.

Rustiq는 평균 Circuit 깊이 면에서 가장 좋은 성능을 보였으며**, SABRE 대비 약 20% 낮은 깊이를 달성했습니다. 이는 Rustiq가 PauliEvolutionGate 연산을 최적화된 저깊이 분해 전략으로 합성하도록 특별히 설계되었기 때문에 예상된 결과입니다. 또한 깊이 플롯을 보면, Circuit 규모와 복잡도가 커질수록 Rustiq가 가장 효과적으로 확장되어 대형 Circuit에서 AI 및 SABRE 모두보다 현저히 낮은 깊이를 유지하는 것을 알 수 있습니다.

AI Transpiler는 Circuit 깊이 면에서 강력하고 일관된 성능을 보여, 대부분의 Circuit에서 SABRE를 꾸준히 능가했습니다. 그러나 특히 대형 Circuit에서 가장 높은 실행 시간을 기록했으며, 이는 시간이 중요한 워크로드에서 실용성을 제한할 수 있습니다. 깊이 측면에서는 견실한 개선을 제공하지만, 실행 시간의 확장성은 여전히 핵심적인 한계입니다.

SABRE는 평균 깊이가 가장 높았으나, AI Transpiler와 근소한 차이로 평균 Gate 수가 가장 낮았습니다. 이는 Gate 수를 직접 최소화하는 SABRE 휴리스틱의 설계 방향과 일치합니다. Rustiq는 깊이를 낮추는 강점에도 불구하고 평균 Gate 수가 가장 높았으며, Circuit의 실행 시간보다 크기가 더 중요한 애플리케이션에서는 고려해야 할 중요한 트레이드오프입니다.

요약

AI Transpiler는 일반적으로 특히 Circuit 깊이 측면에서 SABRE보다 나은 결과를 제공하지만, "항상 AI Transpiler를 사용하라"는 단순한 결론으로 이어져서는 안 됩니다. 고려해야 할 중요한 뉘앙스가 있습니다:

  • AI Transpiler는 일반적으로 신뢰할 수 있으며 깊이 최적화된 Circuit을 제공하지만, 실행 시간 면에서 트레이드오프가 있으며, 지원되는 커플링 맵 및 합성 기능을 포함한 다른 한계도 있습니다. 이에 대한 자세한 내용은 Qiskit Transpiler Service 문서에서 확인할 수 있습니다.

  • 특히 매우 크거나 하드웨어 특화된 Circuit의 경우, AI Transpiler가 그다지 효과적이지 않을 수 있습니다. 이러한 경우 기본 SABRE Transpiler는 여전히 매우 신뢰할 수 있으며, 매개변수를 조정하여 추가로 최적화할 수 있습니다(SABRE 최적화 튜토리얼 참조).

  • 방법을 선택할 때 Circuit 구조를 고려하는 것도 중요합니다. 예를 들어 rustiqPauliEvolutionGate를 포함하는 Circuit을 위해 특별히 제작되어 해밀토니안 시뮬레이션 문제에서 최고의 성능을 발휘하는 경우가 많습니다.

권고사항:

모든 상황에 맞는 단일 트랜스파일 전략은 없습니다. 사용자는 자신의 Circuit 구조를 파악하고, AI, SABRE, Rustiq와 같은 특화 도구를 포함한 여러 트랜스파일 방법을 테스트하여 특정 문제와 하드웨어 제약 조건에 가장 효율적인 솔루션을 찾을 것을 권장합니다.

Step 3: Execute using Qiskit primitives

이 튜토리얼은 트랜스파일에 초점을 맞추므로, 양자 장치에서 실험을 실행하지 않습니다. 목표는 Step 2의 최적화를 활용하여 깊이와 Gate 수가 줄어든 트랜스파일된 Circuit을 얻는 것입니다.

Step 4: Post-process and return result in desired classical format

이 노트북에서는 실행이 없으므로 후처리할 결과도 없습니다.

참고 문헌

[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 Brugière, S. Martiel et al. https://arxiv.org/abs/2404.03280