Pengoptimuman transpilasi dengan SABRE
Anggaran penggunaan: 1 minit pada pemproses Heron r2 (NOTA: Ini hanya anggaran sahaja. Masa larian anda mungkin berbeza.)
Hasil pembelajaran
Selepas menyelesaikan tutorial ini, anda seharusnya faham:
- Cara mengkonfigurasi parameter SABRE (
layout_trials,swap_trials,max_iterations) untuk meningkatkan kualiti transpilasi - Pertukaran antara masa larian transpilasi dan kualiti litar (kedalaman dan bilangan gate)
- Cara menyesuaikan heuristik penghalaan SABRE (
basic,decay,lookahead) dan membandingkan prestasinya pada perkakasan
Prasyarat
Kami mencadangkan anda biasa dengan topik berikut sebelum meneruskan tutorial ini:
- Transpil litar: gambaran keseluruhan transpilasi dalam Qiskit
- Peringkat Transpiler: peringkat susun atur dan penghalaan
- Konfigurasi pengurus laluan pratetap: penyesuaian tahap pengoptimuman
Latar Belakang
Transpilasi menukar litar kuantum kepada bentuk yang serasi dengan perkakasan kuantum tertentu. Dua peringkat utama ialah memilih susun atur qubit (memetakan qubit logikal kepada qubit fizikal) dan penghalaan gate (memasukkan gate SWAP supaya gate berbilang qubit mematuhi sambungan peranti).
SABRE (SWAP-Based Bidirectional heuristic search algorithm) mengoptimumkan kedua-dua susun atur dan penghalaan. Ia sangat berkesan untuk litar berskala besar (100+ qubit) pada peranti dengan peta gandingan yang kompleks, seperti pemproses IBM® Heron. SABRE meminimumkan gate SWAP dan mengurangkan kedalaman litar, meningkatkan ketepatan pelaksanaan. Penambahbaikan terkini dalam algoritma LightSABRE seterusnya mengurangkan masa larian dan bilangan gate.
Dalam tutorial ini, anda akan mengkonfigurasi SabreLayout dengan parameter berbeza untuk mengoptimumkan litar GHZ kecil dan memerhati kesannya terhadap ketepatan pelaksanaan. Kemudian, anda akan membandingkan heuristik penghalaan SABRE pada skala besar menggunakan perkakasan sebenar.
Keperluan
Sebelum memulakan tutorial ini, pastikan anda telah memasang yang berikut:
- Qiskit SDK v2.0 atau lebih baru, dengan sokongan visualisasi
- Qiskit Runtime v0.22 atau lebih baru (
pip install qiskit-ibm-runtime) - Qiskit Aer (
pip install qiskit-aer)
Persediaan
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorOptions
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_aer.primitives import EstimatorV2 as AerEstimator
from qiskit.transpiler.passes import (
SabreLayout,
SabreSwap,
BarrierBeforeFinalMeasurements,
StarPreRouting,
)
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.passmanager.flow_controllers import ConditionalController
import matplotlib.pyplot as plt
import numpy as np
import time
seed = 42
service = QiskitRuntimeService(
channel="ibm_cloud",
token="<YOUR_API_TOKEN>", # Replace with your actual API token
instance="<YOUR_INSTANCE_NAME>", # Replace with your instance name if needed
)
backend = service.least_busy(operational=True, simulator=False)
print(f"Using backend: {backend.name}")
Using backend: ibm_kingston
Contoh simulator berskala kecil
Dalam bahagian ini, simulator berombak berdasarkan model bunyi backend sebenar digunakan untuk menunjukkan bagaimana konfigurasi SabreLayout yang berbeza mempengaruhi kualiti transpilasi dan ketepatan pelaksanaan. Menggunakan qiskit_aer dengan model bunyi yang diperoleh daripada data kalibrasi perkakasan sebenar membolehkan anda menguji transpilasi tanpa menggunakan kredit perkakasan.
Langkah 1: Petakan input klasikal kepada masalah kuantum
Kita membina litar GHZ topologi bintang dengan 15 qubit. Qubit pertama adalah hab, dengan gate CNOT yang menyambungkannya terus ke setiap qubit lain. Topologi ini menimbulkan masalah susun atur yang mencabar kerana ia tidak memetakan secara mudah ke peta gandingan peranti.
Kita juga menakrifkan operator ZZ untuk mengukur korelasi belitan merentasi pasangan qubit.

SABRE ialah algoritma tujuan umum dan tidak membuat andaian tentang struktur litar. Untuk litar GHZ topologi bintang ini, penghalaan optimum sebenarnya diketahui: laluan StarPreRouting mengesan sub-litar bintang dan menulis semula menjadi rantai linear yang boleh dipetakan terus ke mana-mana backend dengan laluan linear yang cukup panjang. Tutorial ini memberi tumpuan kepada SABRE kerana ia berfungsi untuk litar sewenang-wenangnya, tetapi jika anda tahu litar anda mempunyai struktur khas yang jelas, menggunakan laluan khusus seperti StarPreRouting sebelum penghalaan boleh mengatasi mana-mana carian heuristik.
num_qubits_sim = 15
# Create star-topology GHZ circuit
qc_sim = QuantumCircuit(num_qubits_sim)
qc_sim.h(0)
for i in range(1, num_qubits_sim):
qc_sim.cx(0, i)
qc_sim.measure_all()
# ZZ operators: Z on qubit 0 and qubit i, identity elsewhere
operator_strings_sim = [
"Z" + "I" * i + "Z" + "I" * (num_qubits_sim - 2 - i)
for i in range(num_qubits_sim - 1)
]
operators_sim = [SparsePauliOp(op) for op in operator_strings_sim]
Langkah 2: Optimumkan masalah untuk pelaksanaan perkakasan kuantum
Pengurus laluan pratetap optimization_level=3 lalai sudah menggunakan SabreLayout, tetapi dengan tetapan lalai yang konservatif. Untuk meneroka kesan tetapan yang lebih kuat, laluan tersebut digantikan dengan SabreLayout tersuai yang dikonfigurasi untuk carian yang lebih agresif, manakala setiap laluan lain dalam peringkat susun atur dibiarkan tidak berubah. Sebagai titik perbandingan yang berasingan, pengurus laluan keempat mengekalkan SabreLayout lalai tetapi menambahkan StarPreRouting ke peringkat init. StarPreRouting ialah laluan sedar-struktur yang mengesan sub-litar bintang dan menulis semula menjadi rantai linear sebelum penghalaan.
Aliran kerjanya adalah:
- Periksa pengurus laluan lalai untuk melihat di mana
SabreLayoutberada dalam peringkatlayout. - Ganti laluan tersebut dengan instans
SabreLayouttersuai menggunakanPassManager.replace(index, passes=...), dan bina varianpm_stardenganpm.init += StarPreRouting(). - Jalankan keempat-empat pengurus laluan dan bandingkan metriknya.
Empat konfigurasi adalah:
| Konfigurasi | Keterangan |
|---|---|
pm_1 (lalai) | Pratetap tahap-3 lalai (SabreLayout dengan max_iterations=4, layout_trials=20, swap_trials=20) |
pm_2 | SabreLayout tersuai (max_iterations=4, layout_trials=200, swap_trials=200) |
pm_3 | SabreLayout tersuai (max_iterations=8, layout_trials=200, swap_trials=200) |
pm_star | Pratetap lalai dengan StarPreRouting ditambahkan ke peringkat init |
Parameter utama SABRE:
layout_trials/swap_trials: Mengawal bilangan susun atur calon dan penyelesaian penghalaan yang diterokai SABRE. Meningkatkan bilangan ujian bermakna SABRE menyampel ruang carian yang lebih luas, meningkatkan peluang menemui penyelesaian yang lebih baik.max_iterations: Mengawal bilangan kitaran pemurnian penghalaan maju-undur yang dilakukan SABRE pada setiap calon. SABRE secara berulang meningkatkan susun atur dengan belajar daripada maklum balas penghalaan, jadi lebih banyak lelaran bermakna penambahbaikan yang lebih baik.
Kedua-duanya datang dengan kos masa transpilasi yang lebih lama, tetapi litar yang dihasilkan lebih pendek dan menggunakan lebih sedikit gate, yang secara langsung mengurangkan dekoherensi dan ralat gate pada perkakasan sebenar.
Langkah 2a: Periksa pengurus laluan lalai. StagedPassManager terdiri daripada peringkat (init, layout, routing, translation, optimization, scheduling), masing-masing sendiri ialah PassManager. Memanggil .draw() pada peringkat memaparkan laluannya sebagai graf supaya kita dapat melihat di mana SabreLayout berada.
# Build the default pass manager (no modifications yet)
pm_1 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
# Visualize the layout stage to see where SabreLayout sits
pm_1.layout.draw()

Dalam rajah di atas, laluan SabreLayout yang ingin kita sesuaikan berada di dalam ConditionalController pada kedudukan [2] dalam peringkat susun atur. Pengawal tersebut melakukan dua perkara:
- Ia menjaga
SabreLayoutsupaya ia hanya berjalan apabilaVF2Layoutpada [1] gagal menemui pemetaan yang sempurna (jika tidak, susun atur VF2 yang sempurna dikekalkan). - Ia mendahului
SabreLayoutdengan laluanBarrierBeforeFinalMeasurementsyang melindungi pengukuran daripada disusun semula semasa penghalaan dalaman SabreLayout.
Jika kita hanya replace(index=2, passes=sl_2), kedua-dua tingkah laku tersebut akan hilang. Untuk mengekalkannya, kita membungkus semula SabreLayout tersuai kita dalam ConditionalController yang sama (dengan syarat yang sama dan penghalang perlindungan) sebelum menukarnya.
Langkah 2b: Bina laluan SabreLayout tersuai dan gantikan yang lalai.
cmap = backend.coupling_map
# Custom SabreLayout passes with more aggressive search
sl_2 = SabreLayout(
coupling_map=cmap,
seed=seed,
max_iterations=4,
layout_trials=200,
swap_trials=200,
)
sl_3 = SabreLayout(
coupling_map=cmap,
seed=seed,
max_iterations=8,
layout_trials=200,
swap_trials=200,
)
# Same condition the preset uses: only run SabreLayout when VF2Layout did not
# find a perfect mapping. This preserves any perfect layout VF2 produced at [1].
def _vf2_match_not_found(property_set):
if property_set["layout"] is None:
return True
return (
property_set["VF2Layout_stop_reason"] is not None
and property_set["VF2Layout_stop_reason"]
is not VF2LayoutStopReason.SOLUTION_FOUND
)
def wrap_sabre(sabre_pass):
"""Re-wrap a SabreLayout in the original ConditionalController + barrier."""
return ConditionalController(
[
BarrierBeforeFinalMeasurements(
"qiskit.transpiler.internal.routing.protection.barrier"
),
sabre_pass,
],
condition=_vf2_match_not_found,
)
# Build two fresh pass managers and swap in the wrapped custom SabreLayout at index 2
pm_2 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_3 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_2.layout.replace(index=2, passes=wrap_sabre(sl_2))
pm_3.layout.replace(index=2, passes=wrap_sabre(sl_3))
# Build pm_star: default preset with StarPreRouting added to the init stage
pm_star = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_star.init += StarPreRouting()
# Visualize pm_3 after replacement (pm_2 has the same structure, only max_iterations differs)
pm_3.layout.draw()

Kedudukan [2] kini merupakan ConditionalController sekali lagi — sama bentuk dengan lalai, tetapi SabreLayout dalaman adalah yang tersuai (dengan layout_trials=200, swap_trials=200, dan max_iterations=8 untuk pm_3; pm_2 adalah sama kecuali max_iterations=4). Penghalang perlindungan dan penggerbangan _vf2_match_not_found dikekalkan, jadi satu-satunya perbezaan antara pm_2/pm_3 dan pm_1 ialah konfigurasi SABRE itu sendiri. pm_star mengekalkan SabreLayout lalai dan hanya menambahkan StarPreRouting di akhir peringkat init.
Langkah 2c: Jalankan setiap pengurus laluan dan bandingkan.
results_sim = {}
for name, pm in [
("pm_1 (4,20,20)", pm_1),
("pm_2 (4,200,200)", pm_2),
("pm_3 (8,200,200)", pm_3),
("pm_star (default + StarPreRouting)", pm_star),
]:
t0 = time.time()
tqc = pm.run(qc_sim)
elapsed = time.time() - t0
depth = tqc.depth(lambda x: x.operation.num_qubits == 2)
size = tqc.size()
ops_mapped = [op.apply_layout(tqc.layout) for op in operators_sim]
results_sim[name] = {
"tqc": tqc,
"ops": ops_mapped,
"depth": depth,
"size": size,
"time": elapsed,
}
print(f"{name}: 2Q Depth {depth}, Size {size}, Time {elapsed:.2f}s")
# Print improvement relative to default (pm_1)
baseline = results_sim["pm_1 (4,20,20)"]
print("\nImprovement vs. default (pm_1):")
for name in [
"pm_2 (4,200,200)",
"pm_3 (8,200,200)",
"pm_star (default + StarPreRouting)",
]:
r = results_sim[name]
depth_pct = (baseline["depth"] - r["depth"]) / baseline["depth"] * 100
size_pct = (baseline["size"] - r["size"]) / baseline["size"] * 100
print(f" {name}: 2Q depth {depth_pct:+.1f}%, size {size_pct:+.1f}%")
pm_1 (4,20,20): 2Q Depth 38, Size 183, Time 0.01s
pm_2 (4,200,200): 2Q Depth 36, Size 183, Time 0.15s
pm_3 (8,200,200): 2Q Depth 30, Size 158, Time 0.16s
pm_star (default + StarPreRouting): 2Q Depth 26, Size 160, Time 0.01s
Improvement vs. default (pm_1):
pm_2 (4,200,200): 2Q depth +5.3%, size +0.0%
pm_3 (8,200,200): 2Q depth +21.1%, size +13.7%
pm_star (default + StarPreRouting): 2Q depth +31.6%, size +12.6%
Ketiga-tiga pengurus laluan yang diubah suai menghasilkan litar dengan kedalaman 2Q yang lebih rendah berbanding lalai. Konfigurasi SABRE yang agresif (pm_2 dan pm_3) menukar masa transpilasi yang lebih lama untuk carian yang lebih luas, manakala pm_star memanfaatkan struktur bintang litar dan menghasilkan hasil yang lebih cetek tanpa sebarang kos transpilasi tambahan. Peningkatan tepat akan berbeza dari satu larian ke larian yang lain, tetapi trend umum adalah konsisten: lebih banyak ujian dan lelaran SABRE membolehkan carian heuristik dalam ruang yang lebih luas, dan laluan sedar-struktur seperti StarPreRouting boleh memintas carian itu sepenuhnya apabila bentuk litar sepadan.
Walaupun pada skala kecil ini (15 qubit), ruang untuk penambahbaikan sudah cukup supaya ketiga-tiga pendekatan mengatasi lalai. Dengan litar yang lebih besar (100+ qubit), ruang carian berkembang secara mendadak dan manfaat kedua-dua peningkatan ujian serta laluan sedar-struktur menjadi lebih ketara, seperti yang akan ditunjukkan oleh bahagian berskala besar.
pm_names = list(results_sim.keys())
depths = [results_sim[n]["depth"] for n in pm_names]
sizes = [results_sim[n]["size"] for n in pm_names]
times = [results_sim[n]["time"] for n in pm_names]
colors = ["#404080", "#2a9d8f", "#a8d05e", "#e29bdd"]
x = np.arange(len(pm_names))
fig, axs = plt.subplots(1, 3, figsize=(14, 5))
# 2Q Depth
bars = axs[0].bar(x, depths, color=colors)
axs[0].set_ylabel("2Q Depth", fontsize=11)
axs[0].set_title("Two-Qubit Gate Depth", fontsize=13)
axs[0].set_ylim(0, max(depths) * 1.2)
for bar, val in zip(bars, depths):
axs[0].text(
bar.get_x() + bar.get_width() / 2,
bar.get_height() + max(depths) * 0.02,
str(val),
ha="center",
va="bottom",
fontsize=11,
fontweight="bold",
)
for i in range(1, len(depths)):
pct = (depths[0] - depths[i]) / depths[0] * 100
if pct != 0:
axs[0].text(
bars[i].get_x() + bars[i].get_width() / 2,
bars[i].get_height() / 2,
f"{pct:+.0f}%",
ha="center",
va="center",
fontsize=10,
color="white",
fontweight="bold",
)
# Size
bars = axs[1].bar(x, sizes, color=colors)
axs[1].set_ylabel("Gate Count", fontsize=11)
axs[1].set_title("Circuit Size", fontsize=13)
axs[1].set_ylim(0, max(sizes) * 1.2)
for bar, val in zip(bars, sizes):
axs[1].text(
bar.get_x() + bar.get_width() / 2,
bar.get_height() + max(sizes) * 0.02,
str(val),
ha="center",
va="bottom",
fontsize=11,
fontweight="bold",
)
for i in range(1, len(sizes)):
pct = (sizes[0] - sizes[i]) / sizes[0] * 100
if abs(pct) > 0.1:
axs[1].text(
bars[i].get_x() + bars[i].get_width() / 2,
bars[i].get_height() / 2,
f"{pct:+.0f}%",
ha="center",
va="center",
fontsize=10,
color="white",
fontweight="bold",
)
# Time
bars = axs[2].bar(x, times, color=colors)
axs[2].set_ylabel("Time (s)", fontsize=11)
axs[2].set_title("Transpilation Time", fontsize=13)
axs[2].set_ylim(0, max(times) * 1.3)
for bar, val in zip(bars, times):
axs[2].text(
bar.get_x() + bar.get_width() / 2,
bar.get_height() + max(times) * 0.03,
f"{val:.2f}s",
ha="center",
va="bottom",
fontsize=11,
fontweight="bold",
)
for ax in axs:
ax.set_xticks(x)
ax.set_xticklabels(pm_names, fontsize=8, rotation=15)
ax.grid(axis="y", linestyle="--", alpha=0.5)
plt.suptitle(
"Transpilation quality vs. configuration",
fontsize=14,
fontweight="bold",
y=1.02,
)
plt.tight_layout()
plt.show()

Langkah 3: Laksanakan menggunakan primitif Qiskit
Kita menjalankan setiap litar yang ditranspilasi 10 kali menggunakan Aer EstimatorV2 dengan model bunyi yang diperoleh daripada backend sebenar. Memandangkan keputusan simulasi berombak berbeza-beza antara larian, mengambil purata beberapa larian memberikan anggaran ketepatan yang lebih dipercayai dan membolehkan kita mengukur ketidakpastian statistik dengan bar ralat.
# Create a noisy estimator from the real backend's noise model
noisy_estimator = AerEstimator.from_backend(backend)
num_runs = 10
# sim_all_runs[name] = list of arrays, one per run
sim_all_runs = {name: [] for name in results_sim}
for run in range(num_runs):
for name, r in results_sim.items():
job = noisy_estimator.run([(r["tqc"], r["ops"])])
evs = list(job.result()[0].data.evs)
sim_all_runs[name].append(evs)
print(f"Run {run + 1}/{num_runs} done")
# Compute mean and std across runs for each config
sim_stats = {}
for name in results_sim:
all_evs = np.array(sim_all_runs[name]) # shape (num_runs, num_operators)
sim_stats[name] = {
"mean": np.mean(all_evs, axis=0),
"std": np.std(all_evs, axis=0),
"overall_mean": np.mean(all_evs),
"overall_std": np.std(
np.mean(all_evs, axis=1)
), # std of per-run averages
}
print(
f"{name}: mean fidelity = {sim_stats[name]['overall_mean']:.4f} +/- {sim_stats[name]['overall_std']:.4f}"
)
Run 1/10 done
Run 2/10 done
Run 3/10 done
Run 4/10 done
Run 5/10 done
Run 6/10 done
Run 7/10 done
Run 8/10 done
Run 9/10 done
Run 10/10 done
pm_1 (4,20,20): mean fidelity = 0.9510 +/- 0.0094
pm_2 (4,200,200): mean fidelity = 0.9513 +/- 0.0043
pm_3 (8,200,200): mean fidelity = 0.9540 +/- 0.0065
pm_star (default + StarPreRouting): mean fidelity = 0.9547 +/- 0.0072
Kerana ini ialah litar kecil, nilai ketepatan mendarat agak dekat merentasi semua empat konfigurasi. Litar-litar itu cukup pendek sehingga bunyi perkakasan tidak membebankan terlalu berat walaupun versi yang paling kurang dioptimumkan. Purata ketepatan secara amnya mengikuti kedalaman 2Q: pm_3 dan pm_star, dua litar paling cetek, mencapai ketepatan tertinggi dan pada dasarnya seimbang dalam bar ralat masing-masing. pm_2 ialah contoh balas yang berguna: walaupun kedalaman 2Q-nya lebih rendah daripada pm_1, purata ketepatannya sedikit lebih rendah juga, yang mengingatkan bahawa hubungan kedalaman-ke-ketepatan adalah statistik bukan deterministik. Qubit fizikal tertentu yang dipilih susun atur dan kalibrasi qubit tersebut pada masa larian juga penting.
Langkah 4: Proses selepas dan kembalikan hasil dalam format klasikal yang dikehendaki
Seterusnya, plot korelasi belitan sebagai fungsi jarak qubit, bersama purata korelasi sebagai metrik ketepatan tunggal. Dalam kes ideal (tanpa bunyi), semua korelasi akan bernilai 1. Dengan bunyi realistik, setiap gate tambahan memperkenalkan ralat dan setiap langkah masa tambahan membenarkan dekoherensi, jadi litar yang ditranspilasi dengan kedalaman dan gate yang lebih rendah (terutamanya gate dua-qubit) seharusnya memelihara belitan dengan lebih baik.
data_sim = list(range(1, len(operators_sim) + 1))
markers = ["o", "s", "^", "*"]
colors_line = ["#404080", "#2a9d8f", "#a8d05e", "#e29bdd"]
fig, (ax1, ax2) = plt.subplots(
1, 2, figsize=(14, 5), gridspec_kw={"width_ratios": [2.5, 1]}
)
# Left: correlations vs distance with error bars (mean +/- 1 std)
for (name, stats), marker, color in zip(
sim_stats.items(), markers, colors_line
):
ax1.errorbar(
data_sim,
stats["mean"],
yerr=stats["std"],
marker=marker,
label=name,
color=color,
linewidth=2,
capsize=3,
capthick=1,
elinewidth=1,
)
ax1.set_xlabel("Distance between qubits $i$", fontsize=11)
ax1.set_ylabel(r"$\langle Z_0 Z_i \rangle$", fontsize=11)
ax1.set_title(
"Entanglement correlations vs. qubit distance (avg. of 10 runs)",
fontsize=12,
)
ax1.legend(fontsize=9)
ax1.grid(alpha=0.3)
# Right: mean correlation bar chart with error bars
names = list(sim_stats.keys())
means = [sim_stats[n]["overall_mean"] for n in names]
stds = [sim_stats[n]["overall_std"] for n in names]
x_bar = np.arange(len(names))
bars = ax2.bar(
x_bar, means, yerr=stds, color=colors_line, capsize=5, ecolor="gray"
)
ax2.set_ylabel(r"Mean $\langle Z_0 Z_i \rangle$", fontsize=11)
ax2.set_title("Average fidelity", fontsize=13, pad=12)
y_range = max(means) - min(means) if max(means) != min(means) else 0.01
# Top of ylim accounts for the bar height + std error bar + headroom for the value label
y_top = max(m + s for m, s in zip(means, stds)) + y_range * 1.5
ax2.set_ylim(min(means) - y_range * 0.8, y_top)
for bar, val, std in zip(bars, means, stds):
ax2.text(
bar.get_x() + bar.get_width() / 2,
bar.get_height() + std + y_range * 0.15,
f"{val:.4f}",
ha="center",
va="bottom",
fontsize=10,
fontweight="bold",
)
# Annotate % change vs pm_1
baseline_mean = means[0]
for i in range(1, len(means)):
pct = (means[i] - baseline_mean) / baseline_mean * 100
if abs(pct) > 0.01:
mid_y = (means[i] + ax2.get_ylim()[0]) / 2
ax2.text(
bars[i].get_x() + bars[i].get_width() / 2,
mid_y,
f"{pct:+.1f}%",
ha="center",
va="center",
fontsize=10,
color="white",
fontweight="bold",
)
ax2.set_xticks(x_bar)
ax2.set_xticklabels(names, fontsize=8, rotation=15)
ax2.grid(axis="y", linestyle="--", alpha=0.5)
fig.tight_layout()
plt.show()

Hasilnya menunjukkan hubungan yang jelas antara kualiti transpilasi dan ketepatan pelaksanaan, dengan beberapa peringatan yang berguna:
pm_1(lalai): Garis dasar. Dengan hanya 20 ujian dan empat lelaran, SABRE mempunyai ruang terhad untuk mengoptimumkan, menghasilkan litar terdalam antara litar hanya-SABRE.pm_2(lebih banyak ujian): Meneroka sepuluh kali lebih banyak calon mendapati susun atur yang sedikit lebih cetek, tetapi purata ketepatan agak rata (dan boleh jatuh di bawah garis dasar dalam lingkungan bunyi) kerana peningkatan kedalaman adalah kecil pada skala ini.pm_3(lebih banyak ujian + lebih banyak lelaran): Menggandakanmax_iterationskepada 8 memberikan SABRE lebih banyak kitaran pemurnian, menghasilkan litar hanya-SABRE yang paling cetek dan purata ketepatan tertinggi dalam perbandingan.pm_star(lalai + StarPreRouting): MenambahkanStarPreRoutingke peringkat init pratetap yang pada dasarnya lalai. Penulisan semula sedar-struktur meruntuhkan bintang menjadi rantai linear yang dipetakan oleh baki transpiler ke laluan linear peranti, menghasilkan litar paling cetek keseluruhan (sedikit lebih baik daripadapm_3) dan sepadan denganpm_3dari segi ketepatan dalam bar ralat. Ia melakukan ini dengan masa transpilasi yang sama dengan lalai, kerana penulisan semula pada dasarnya percuma berbanding carian stokastik SABRE.
Perlu diambil perhatian bahawa meningkatkan max_iterations tidak sentiasa memberikan kesan positif. Dalam kes ini ia membantu dengan ketara, tetapi untuk litar atau backend lain lelaran tambahan mungkin tidak menghasilkan penambahbaikan selanjutnya, atau bahkan sedikit menjejaskan prestasi akibat pengoptimuman berlebihan minimum setempat. Secara umum, anda seharusnya meningkatkan layout_trials dan swap_trials sebanyak mungkin mengikut bajet masa anda, kerana lebih banyak ujian sentiasa meningkatkan peluang menemui susun atur yang lebih baik. Meningkatkan max_iterations patut dicuba tetapi harus disahkan untuk kes penggunaan anda yang spesifik. Laluan khusus seperti StarPreRouting serupa dalam semangat tetapi lebih bergantung kepada litar: ia hanya membantu apabila litar sebenarnya mengandungi struktur yang disasarkan. Faedahnya adalah besar apabila boleh digunakan dan sifar sebaliknya, tetapi ia pada dasarnya percuma untuk dicuba.
Contoh perkakasan berskala besar
Selain menyesuaikan bilangan ujian, SABRE menyokong penyesuaian heuristik penghalaan. SABRE menawarkan tiga heuristik:
basic: Pendekatan tamak ringkas yang memilih swap yang meminimumkan jarak segera ke gate seterusnya.decay(lalai): Menimbang qubit secara dinamik berdasarkan aktiviti terkini, mengecilkan swap berulang pada qubit yang sama.lookahead: Menilai kos penghalaan masa depan dengan melihat ke depan pada gate yang akan datang, berpotensi menemui jujukan swap yang lebih baik.
Untuk menggunakan heuristik tersuai, cipta laluan SabreSwap dan sambungkannya ke SabreLayout melalui parameter routing_pass.
Pengurus laluan keempat ditambahkan ke dalam perbandingan: pm_star_hw, yang mengekalkan tetapan SabreLayout/SabreSwap lalai tetapi menambahkan StarPreRouting ke peringkat init. Pada skala ini (100 qubit) carian SABRE lebih sukar, dan penulisan semula dari bintang ke rantai linear menjadi kemenangan yang jelas kerana pemproses Heron mempunyai laluan linear yang cukup panjang untuk menempatkan litar yang dihasilkan.
Di sini kita membandingkan ketiga-tiga heuristik SABRE ditambah StarPreRouting pada skala besar pada litar GHZ 100 qubit. Kita menjalankan beberapa ujian susun atur dengan seed berbeza untuk konfigurasi SABRE, memilih litar yang ditranspilasi terbaik daripada setiap satu, dan menghantar semuanya ke perkakasan sebenar bersama-sama dengan hasil StarPreRouting.
Langkah 1-4 dipadatkan dalam satu blok kod
Di sini aliran kerja penuh digabungkan pada skala yang lebih besar. Apabila menggunakan SabreSwap sebagai routing_pass untuk SabreLayout, hanya satu ujian susun atur dilakukan setiap panggilan, jadi sel kod berikut melakukan gelung ke atas seed untuk meneroka ruang susun atur.
Kita menggunakan pembantu wrap_sabre yang sama yang ditakrifkan dalam Langkah 2 berskala kecil (di atas), dan menambah pembantu wrap_routing yang serupa kerana peringkat routing pada indeks [1] juga merupakan ConditionalController([BarrierBeforeFinalMeasurements, routing_pass], ...) — menggantikannya secara langsung juga akan menggugurkan penghalang perlindungan dan penggerbangan _swap_condition.
# -------------------------Step 1-------------------------
num_qubits = 100
# Create star-topology GHZ circuit
qc = QuantumCircuit(num_qubits)
qc.h(0)
for i in range(1, num_qubits):
qc.cx(0, i)
qc.measure_all()
# ZZ operators
operator_strings = [
"Z" + "I" * i + "Z" + "I" * (num_qubits - 2 - i)
for i in range(num_qubits - 1)
]
operators = [SparsePauliOp(op) for op in operator_strings]
# -------------------------Step 2-------------------------
num_seeds = 10
seed_list = [seed + i for i in range(num_seeds)]
swap_trials = 200
# The default routing[1] is a ConditionalController([barrier, routing_pass],
# condition=_swap_condition); we re-wrap so the new routing pass keeps the
# protective barrier and is skipped when routing isn't needed (matches the preset).
def _swap_condition(property_set):
return not property_set["routing_not_needed"]
def wrap_routing(routing_pass):
return ConditionalController(
[
BarrierBeforeFinalMeasurements(
"qiskit.transpiler.internal.routing.protection.barrier"
),
routing_pass,
],
condition=_swap_condition,
)
heuristic_results = {}
# Three SABRE heuristics, swept over seeds
for heuristic in ["basic", "decay", "lookahead"]:
trials = []
for s in seed_list:
sr = SabreSwap(
coupling_map=cmap, heuristic=heuristic, trials=swap_trials, seed=s
)
sl = SabreLayout(coupling_map=cmap, routing_pass=sr, seed=s)
pm = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=s
)
# Re-wrap each custom pass in its original ConditionalController + barrier
# (wrap_sabre is defined in the small-scale Step 2 cell above).
pm.layout.replace(index=2, passes=wrap_sabre(sl))
pm.routing.replace(index=1, passes=wrap_routing(sr))
t0 = time.time()
tqc = pm.run(qc)
elapsed = time.time() - t0
depth = tqc.depth(lambda x: x.operation.num_qubits == 2)
size = tqc.size()
trials.append(
{
"tqc": tqc,
"depth": depth,
"size": size,
"time": elapsed,
"seed": s,
}
)
heuristic_results[heuristic] = trials
# Default preset + StarPreRouting in init, also swept over seeds for a fair comparison
star_trials = []
for s in seed_list:
pm_star_hw = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=s
)
pm_star_hw.init += StarPreRouting()
t0 = time.time()
tqc = pm_star_hw.run(qc)
elapsed = time.time() - t0
depth = tqc.depth(lambda x: x.operation.num_qubits == 2)
size = tqc.size()
star_trials.append(
{
"tqc": tqc,
"depth": depth,
"size": size,
"time": elapsed,
"seed": s,
}
)
heuristic_results["StarPreRouting"] = star_trials
# Print summary for each entry
for label in ["basic", "decay", "lookahead", "StarPreRouting"]:
trials = heuristic_results[label]
depths = [t["depth"] for t in trials]
sizes = [t["size"] for t in trials]
best = min(trials, key=lambda t: t["depth"])
print(f"{label}:")
print(
f" 2Q depth: min: {min(depths)}, mean: {np.mean(depths):.1f}, std: {np.std(depths):.1f}"
)
print(
f" size : min: {min(sizes)}, mean: {np.mean(sizes):.1f}, std: {np.std(sizes):.1f}"
)
print(
f" best seed: {best['seed']} (2Q depth={best['depth']}, size={best['size']})"
)
basic:
2Q depth: min: 524, mean: 570.5, std: 39.9
size : min: 3819, mean: 4227.1, std: 360.6
best seed: 51 (2Q depth=524, size=3852)
decay:
2Q depth: min: 387, mean: 436.4, std: 41.7
size : min: 2687, mean: 3183.1, std: 459.3
best seed: 45 (2Q depth=387, size=2786)
lookahead:
2Q depth: min: 364, mean: 424.6, std: 36.5
size : min: 2335, mean: 3014.6, std: 388.1
best seed: 51 (2Q depth=364, size=2485)
StarPreRouting:
2Q depth: min: 196, mean: 196.0, std: 0.0
size : min: 1151, mean: 1151.0, std: 0.0
best seed: 42 (2Q depth=196, size=1151)
hw_colors = {
"basic": "#ff7f0e",
"decay": "#d62728",
"lookahead": "#1f77b4",
"StarPreRouting": "#2a9d8f",
}
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))
for label in ["basic", "decay", "lookahead", "StarPreRouting"]:
trials = heuristic_results[label]
depths = [t["depth"] for t in trials]
sizes = [t["size"] for t in trials]
seeds = [t["seed"] for t in trials]
color = hw_colors[label]
ax1.scatter(
seeds,
depths,
label=label,
color=color,
alpha=0.8,
edgecolor="k",
s=60,
)
ax1.axhline(np.mean(depths), color=color, linestyle="--", alpha=0.5)
ax2.scatter(
seeds,
sizes,
label=label,
color=color,
alpha=0.8,
edgecolor="k",
s=60,
)
ax2.axhline(np.mean(sizes), color=color, linestyle="--", alpha=0.5)
ax1.set_xlabel("Seed", fontsize=11)
ax1.set_ylabel("2Q Depth", fontsize=11)
ax1.set_title("Two-Qubit Gate Depth per Seed", fontsize=13)
ax1.legend(fontsize=10)
ax1.grid(alpha=0.3)
ax2.set_xlabel("Seed", fontsize=11)
ax2.set_ylabel("Gate Count", fontsize=11)
ax2.set_title("Circuit Size per Seed", fontsize=13)
ax2.legend(fontsize=10)
ax2.grid(alpha=0.3)
plt.suptitle(
"Transpilation variability across seeds: SABRE heuristics vs. StarPreRouting",
fontsize=14,
fontweight="bold",
y=1.02,
)
plt.tight_layout()
plt.show()
# Summary comparison
for label in ["basic", "decay", "lookahead", "StarPreRouting"]:
best = min(heuristic_results[label], key=lambda t: t["depth"])
print(
f"{label}: best 2Q depth={best['depth']}, size={best['size']} (seed={best['seed']})"
)

basic: best 2Q depth=524, size=3852 (seed=51)
decay: best 2Q depth=387, size=2786 (seed=45)
lookahead: best 2Q depth=364, size=2485 (seed=51)
StarPreRouting: best 2Q depth=196, size=1151 (seed=42)
# -------------------------Step 3: Execute on hardware-------------------------
best_circuits = {}
for label in ["basic", "decay", "lookahead", "StarPreRouting"]:
best_circuits[label] = min(
heuristic_results[label], key=lambda t: t["depth"]
)
b = best_circuits[label]
print(f"Best {label}: 2Q depth={b['depth']}, size={b['size']}")
options = EstimatorOptions()
options.resilience_level = 2
options.dynamical_decoupling.enable = True
options.dynamical_decoupling.sequence_type = "XY4"
estimator = Estimator(backend, options=options)
hw_jobs = {}
hw_ops = {}
for label, best in best_circuits.items():
hw_ops[label] = [op.apply_layout(best["tqc"].layout) for op in operators]
hw_jobs[label] = estimator.run([(best["tqc"], hw_ops[label])])
print(f"{label} job: {hw_jobs[label].job_id()}")
estimator.options.environment.job_tags = ["TUT_TOWS"]
hw_results = {}
for label, job in hw_jobs.items():
hw_results[label] = job.result()[0]
print(f"{label} job done")
Best basic: 2Q depth=524, size=3852
Best decay: 2Q depth=387, size=2786
Best lookahead: 2Q depth=364, size=2485
Best StarPreRouting: 2Q depth=196, size=1151
basic job: d81q5tnoha1c73bknprg
decay job: d81q5tugbeec73aktopg
lookahead job: d81q5to0bvlc73d1epe0
StarPreRouting job: d81q5u7tjchs73bn82hg
basic job done
decay job done
lookahead job done
StarPreRouting job done
# -------------------------Step 4: Post-process-------------------------
data = list(range(1, len(operators) + 1))
hw_markers = {
"basic": "D",
"decay": "o",
"lookahead": "s",
"StarPreRouting": "*",
}
hw_labels = ["basic", "decay", "lookahead", "StarPreRouting"]
fig, (ax1, ax2) = plt.subplots(
1, 2, figsize=(14, 5), gridspec_kw={"width_ratios": [2.5, 1]}
)
# Left: correlations vs distance
for label in hw_labels:
evs = list(hw_results[label].data.evs)
b = best_circuits[label]
ax1.plot(
data,
evs,
marker=hw_markers[label],
color=hw_colors[label],
linewidth=2,
label=f"{label} (2Q depth={b['depth']}, size={b['size']})",
markersize=5 if label == "StarPreRouting" else 4,
)
ax1.set_xlabel("Distance between qubits $i$", fontsize=11)
ax1.set_ylabel(r"$\langle Z_0 Z_i \rangle$", fontsize=11)
ax1.set_title(
"Entanglement correlations vs. qubit distance (hardware)", fontsize=12
)
ax1.legend(fontsize=9)
ax1.grid(alpha=0.3)
# Right: mean fidelity bar chart
hw_means = [np.mean(list(hw_results[label].data.evs)) for label in hw_labels]
hw_bar_colors = [hw_colors[label] for label in hw_labels]
x_bar = np.arange(len(hw_labels))
bars = ax2.bar(x_bar, hw_means, color=hw_bar_colors)
ax2.set_ylabel(r"Mean $\langle Z_0 Z_i \rangle$", fontsize=11)
ax2.set_title("Average fidelity", fontsize=13)
y_range = (
max(hw_means) - min(hw_means) if max(hw_means) != min(hw_means) else 0.01
)
ax2.set_ylim(min(hw_means) - y_range * 0.2, max(hw_means) + y_range * 0.15)
for bar, val in zip(bars, hw_means):
ax2.text(
bar.get_x() + bar.get_width() / 2,
bar.get_height() + y_range * 0.05,
f"{val:.4f}",
ha="center",
va="bottom",
fontsize=11,
fontweight="bold",
)
ax2.set_xticks(x_bar)
ax2.set_xticklabels(hw_labels, fontsize=9, rotation=15)
ax2.grid(axis="y", linestyle="--", alpha=0.5)
fig.tight_layout()
plt.show()
print("\nMean fidelity:")
for label, m in zip(hw_labels, hw_means):
print(f" {label}: {m:.4f}")

Mean fidelity:
basic: 0.0344
decay: 0.1298
lookahead: 0.1857
StarPreRouting: 0.3295
Analisis
Plot serakan menunjukkan kebolehubahan yang ketara merentasi seed untuk ketiga-tiga heuristik SABRE, yang menekankan kepentingan menjalankan beberapa ujian susun atur daripada bergantung pada satu transpilasi sahaja. Garis StarPreRouting pada dasarnya rata merentasi seed kerana penulisan semula dari bintang ke rantai linear adalah deterministik berdasarkan struktur; penghalaan SABRE hilir kemudiannya mempunyai sangat sedikit kebebasan pada rantai linear, jadi seed hampir tidak memberi kesan pada kedalaman atau saiz akhir.
Daripada keputusan transpilasi, kedua-dua heuristik decay dan lookahead secara konsisten mengatasi basic dengan margin yang besar. Heuristik basic, walaupun pantas, menggunakan strategi tamak ringkas yang sering menghasilkan litar yang jauh lebih dalam. Untuk litar GHZ topologi bintang ini, lookahead cenderung menghasilkan kedalaman 2Q dan bilangan gate terendah antara heuristik SABRE, kerana fungsi kos ke-depannya sangat sesuai untuk litar dengan corak sambungan jarak jauh. Namun StarPreRouting mengatasi ketiga-tiganya dengan margin yang besar: dengan menulis semula bintang menjadi rantai linear sebelum penghalaan, ia memintas masalah carian sepenuhnya dan menghasilkan litar yang boleh dipetakan oleh baki transpiler ke laluan linear dengan SWAP tambahan yang minimum.
Kelebihan itu terbawa terus ke ketepatan perkakasan. Kedalaman 2Q dan bilangan gate yang lebih rendah tidak selalu diterjemahkan satu-per-satu kepada ketepatan yang lebih tinggi (qubit fizikal tertentu yang digunakan susun atur dan kalibrasinya pada masa larian juga penting), tetapi apabila jurang kedalaman sebesar antara SABRE dan StarPreRouting di sini, pendekatan sedar-struktur menang dengan jelas kerana litar mengumpul jauh lebih sedikit dekoherensi dan peristiwa ralat gate dua-qubit. Carta bar ketepatan menunjukkan StarPreRouting jauh mendahului heuristik SABRE terbaik, manakala basic berada jauh di bawah yang lain kerana litar yang jauh lebih dalam mengumpul paling banyak ralat.
Kesimpulan utama:
- Antara heuristik SABRE,
decaydanlookaheadjauh lebih baik daripadabasicuntuk litar bukan-trivial. Lebih baik pilih salah satu daripada keduanya untuk beban kerja produksi. - Heuristik SABRE terbaik bergantung pada litar dan perkakasan anda. Menguji beberapa heuristik dengan beberapa seed ialah strategi yang paling dipercayai.
- Jika anda ingin meneroka lebih banyak susun atur, tingkatkan
swap_trials(danlayout_trialsapabila anda tidak menetapkan laluan penghalaan tersuai) daripada menyebarkan kerja ke nod jauh. Laluan SABRE sudah memparalelkan ujian merentasi thread tempatan, dan kerja setiap ujian cukup kecil sehingga overhead pengedaran biasanya mengatasi sebarang peningkatan kelajuan. - Apabila litar mempunyai struktur khas yang diketahui, menggunakan laluan sedar-struktur seperti
StarPreRoutingsebelum SABRE boleh memberikan peningkatan magnitud tertib yang tidak dapat dipadankan oleh sebarang penalaan SABRE. Ini bukan pengganti SABRE:StarPreRoutinghanya membantu apabila litar sebenarnya mengandungi sub-litar bintang dan backend mempunyai laluan linear yang cukup panjang. Ia patut diperiksa dalam pustaka laluan bila-bila masa anda mengetahui bentuk litar anda.
Langkah seterusnya
Jika anda mendapati kerja ini menarik, anda mungkin berminat dengan bahan berikut:
- Rujukan API
SabreLayout: dokumentasi parameter penuh - Kertas SABRE: algoritma SABRE asal untuk susun atur dan penghalaan
- Kertas LightSABRE: penambahbaikan algoritma yang menguasai implementasi SABRE semasa Qiskit
- Tulis laluan transpiler tersuai: bina logik transpilasi anda sendiri
- Plugin Transpiler: perluas saluran paip transpilasi Qiskit dengan laluan pihak ketiga
- Perwakilan DAG: fahami graf tak berlingkar berarah yang digunakan secara dalaman oleh transpiler
Kaji selidik tutorial
Sila ambil kaji selidik ringkas ini untuk memberikan maklum balas tentang tutorial ini. Pandangan anda akan membantu kami meningkatkan kandungan dan pengalaman pengguna kami.
Nota: Kaji selidik ini adalah oleh IBM Quantum dan meliputi kandungan tutorial (ditulis oleh IBM). doQumentation menyediakan laman web, terjemahan, dan pelaksanaan kod — untuk maklum balas tentang perkara tersebut, sila buka isu GitHub.