Circuit cutting untuk kondisi batas periodik
Perkiraan penggunaan: 2 menit pada ibm_boston (CATATAN: Ini hanya perkiraan. Waktu proses kamu mungkin berbeda.)
Hasil pembelajaran
-
Pakai koneksi virtual untuk mentranspilasi rantai periodik tanpa me-routing gate batasnya.
-
Ganti gate-gate itu dengan eksperimen circuit-cutting dan rekonstruksi nilai ekspektasi dari hasil Sampler.
-
Bandingkan circuit yang dipotong dan tidak dipotong, dengan memperhitungkan kedalaman circuit dan biaya sampling.
Prasyarat
Kamu sebaiknya sudah familier dengan pola Qiskit, transpilasi, dan primitif Sampler.
Latar Belakang
Kondisi batas periodik menghubungkan qubit terakhir dalam sebuah rantai ke qubit pertama. Kalau kedua qubit ini berjauhan di prosesor kuantum, mengimplementasikan interaksinya butuh routing, yang bisa menambah gate dua qubit dan memperbesar kedalaman circuit.
Gate cutting mengganti gate dua qubit dengan kombinasi berbobot dari operasi qubit tunggal dan pengukuran. Menjalankan eksperimen-eksperimen ini dan menggabungkan hasilnya merekonstruksi nilai ekspektasi untuk circuit asli. Ini bisa mengurangi overhead routing, dengan harga sampling tambahan. Lihat penjelasan circuit cutting untuk dekomposisi kuasiprobabilitas yang mendasarinya.
Pertama-tama kamu akan memvalidasi workflow ini pada simulator tanpa noise berkapasitas enam qubit. Lalu kamu akan menerapkannya pada rantai dengan setidaknya 30 qubit di perangkat keras dan membandingkan hasil yang dipotong dan tidak dipotong. Circuit ini memakai parameter dengan nilai ekspektasi yang diketahui secara analitis, sehingga perbandingan di perangkat keras tidak membutuhkan simulasi statevector yang besar.
Persyaratan
Sebelum memulai tutorial ini, instal:
-
Qiskit SDK v2.3 atau lebih baru, dengan dukungan visualisasi
-
Qiskit Runtime v0.46 atau lebih baru (
pip install 'qiskit-ibm-runtime~=0.46.1') -
Addon Qiskit Circuit cutting v0.10.0 (
pip install 'qiskit-addon-cutting~=0.10.0') -
Qiskit Aer v0.17.2 atau lebih baru (
pip install 'qiskit-aer~=0.17.2')
Contoh perangkat keras juga membutuhkan akun IBM Quantum® Platform dengan akses ke QPU yang punya setidaknya 120 qubit dan kredensial yang tersimpan. Contoh simulator berjalan tanpa akun.
Pengaturan
Impor alat-alat yang dipakai di kedua contoh. Aer mendukung pengukuran perantara yang diperkenalkan oleh gate cutting.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_cutting": "qiskit-addon-cutting", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
from qiskit.circuit.library import n_local
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.quantum_info import SparsePauliOp, Statevector
from qiskit.result import sampled_expectation_value
from qiskit.transpiler import CouplingMap, PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
GateDirection,
Optimize1qGatesDecomposition,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_aer.primitives import SamplerV2 as AerSampler
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import (
Batch,
QiskitRuntimeService,
SamplerOptions,
SamplerV2,
)
Contoh simulator skala kecil
Pakai cincin enam qubit dengan dua lapisan gate pengentang. Perangkat yang disimulasikan punya konektivitas linear, jadi sisi penutup cincin membutuhkan routing kecuali dipotong.
Langkah 1: Petakan input klasik ke masalah kuantum
Fungsi berikut membuat lapisan rotasi RX yang diselingi gate CX pada sisi-sisi cincin yang bergantian. Dua repetisi memberi dua gate batas untuk dipotong. Atur dua lapisan rotasi pertama ke nol dan lapisan terakhir ke . Output idealnya kemudian adalah , hingga fase global.
Ukur ekspektasi Z qubit tunggal rata-rata dan ekspektasi ZZ bersebelahan rata-rata, termasuk pasangan periodik:
Nilai idealnya masing-masing adalah dan . Parameter ini menyediakan tolok ukur untuk workflow; ini tidak mendemonstrasikan komputasi yang sulit secara klasik.
def build_periodic_problem(num_qubits, num_reps=2):
"""Build a periodic benchmark circuit and its Z and ZZ observables."""
edges = [(i, (i + 1) % num_qubits) for i in range(num_qubits)]
circuit = n_local(
num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=edges[::2] + edges[1::2],
reps=num_reps,
)
parameters = np.zeros(circuit.num_parameters)
parameters[-num_qubits:] = np.pi
circuit = circuit.assign_parameters(parameters)
terms = [("Z", [i], 1) for i in range(num_qubits)]
terms += [("ZZ", [i, (i + 1) % num_qubits], 1) for i in range(num_qubits)]
observables = SparsePauliOp.from_sparse_list(terms, num_qubits)
return circuit, observables
num_qubits = 6
circuit, observables = build_periodic_problem(num_qubits)
ideal_expvals = np.array(
[
Statevector(circuit).expectation_value(p).real
for p in observables.paulis
]
)
print("Ideal averages:", ideal_expvals.reshape(2, num_qubits).mean(axis=1))
circuit.draw("mpl", style="iqp", fold=60)
Ideal averages: [-1. 1.]

Langkah 2: Optimasi masalah untuk eksekusi di perangkat keras kuantum
Buat model perangkat dengan coupling map linear. Model ini menyediakan target kompilasi; eksekusi di Langkah 3 akan tanpa noise.
simulation_backend = GenericBackendV2(
num_qubits=num_qubits,
basis_gates=["id", "rz", "sx", "x", "cx"],
coupling_map=CouplingMap.from_line(num_qubits),
seed=1234,
)
initial_layout = list(range(num_qubits))
Tambahkan koneksi virtual antara qubit fisik di ujung-ujung rantai. Lakukan transpile sekali menggunakan coupling map yang diperluas ini dan basis CX. Koneksi virtual mencegah Transpiler melakukan routing pada gate batas, tetapi circuit yang dihasilkan belum bisa dijalankan di perangkat nyata.
Temukan dan potong kedua gate CX batas, terapkan layout virtual ke observable, lalu hasilkan semua suku dekomposisi dengan num_samples=np.inf. Dengan begitu koefisien dekomposisi menjadi eksak; pengukurannya tetap memakai jumlah shot yang terbatas.
Terakhir, terjemahkan eksperimen ke gate native perangkat dan koreksi arahnya bila perlu. Pass-pass ini mempertahankan layout qubit fisik dan menghindari pengulangan pemilihan layout serta routing untuk setiap eksperimen. Helper ini mengembalikan observable terpetakan yang dibutuhkan untuk rekonstruksi.
def prepare_cut_experiments(circuit, observables, backend, initial_layout):
"""Compile with a virtual edge, cut it, and translate to native gates."""
boundary = {initial_layout[0], initial_layout[-1]}
virtual_map = CouplingMap(list(backend.coupling_map.get_edges()))
virtual_map.make_symmetric()
virtual_map.add_edge(initial_layout[-1], initial_layout[0])
virtual_map.add_edge(initial_layout[0], initial_layout[-1])
virtual_pm = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_map,
basis_gates=["rz", "sx", "x", "cx"],
initial_layout=initial_layout,
seed_transpiler=1234,
)
virtual_circuit = virtual_pm.run(circuit)
cut_indices = [
i
for i, instruction in enumerate(virtual_circuit.data)
if instruction.operation.name == "cx"
and {virtual_circuit.find_bit(q).index for q in instruction.qubits}
== boundary
]
mapped_observables = observables.apply_layout(virtual_circuit.layout)
qpd_circuit, bases = cut_gates(virtual_circuit, cut_indices)
experiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=mapped_observables.paulis,
num_samples=np.inf,
)
native_pm = PassManager(
[
BasisTranslator(
SessionEquivalenceLibrary,
target_basis=backend.operation_names,
target=backend.target,
),
GateDirection(backend.coupling_map, target=backend.target),
BasisTranslator(
SessionEquivalenceLibrary,
target_basis=backend.operation_names,
target=backend.target,
),
Optimize1qGatesDecomposition(target=backend.target),
]
)
isa_experiments = native_pm.run(experiments)
print(f"Boundary gates cut: {len(cut_indices)}")
print(f"Number of experiments: {len(isa_experiments)}")
print(
f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}"
)
return isa_experiments, coefficients, mapped_observables
isa_experiments, coefficients, mapped_observables = prepare_cut_experiments(
circuit, observables, simulation_backend, initial_layout
)
Boundary gates cut: 2
Number of experiments: 36
Sampling overhead: 81.0
Setiap dekomposisi CX punya enam suku. Dengan dua pemotongan dan satu basis pengukuran Z bersama, ini menghasilkan eksperimen. Overhead sampling adalah besaran yang berbeda: jumlah kuadrat dari koefisien dekomposisi absolut adalah . Ini menggambarkan biaya sampling untuk menjaga presisi pada rekonstruksi quasiprobability. Lihat penjelasan overhead sampling.
Sebagai pembanding, lakukan transpile pada salinan circuit yang tidak dipotong (dengan pengukuran) menggunakan coupling map yang sebenarnya. measure_all() mencatat qubit logis asli di register meas; transpilation mempertahankan pemetaan bit klasik tersebut, sehingga hasil hitungnya bisa dievaluasi dengan observable asli.
uncut_pm = generate_preset_pass_manager(
optimization_level=1,
backend=simulation_backend,
initial_layout=initial_layout,
seed_transpiler=1234,
)
isa_uncut = uncut_pm.run(circuit.measure_all(inplace=False))
print("Uncut circuit depth:", isa_uncut.depth())
print("Cut circuit depths:", sorted({qc.depth() for qc in isa_experiments}))
Uncut circuit depth: 15
Cut circuit depths: [6, 7, 8, 9, 10, 11, 12, 15]
Langkah 3: Eksekusi menggunakan primitif Qiskit
Jalankan kedua versi dengan primitive Aer Sampler V2. Setiap eksperimen potongan mendapat 10.000 shot, begitu juga circuit tunggal yang tidak dipotong. Jadi alur kerja pemotongan memakai shot 36 kali lebih banyak dalam perbandingan ini. Jumlah shot per circuit yang sama tidak berarti anggaran eksekusi totalnya sama.
shots = 10_000
sampler = AerSampler(seed=1234)
cut_result = sampler.run(isa_experiments, shots=shots).result()
uncut_result = sampler.run([isa_uncut], shots=shots).result()
Langkah 4: Pasca-proses dan kembalikan hasil dalam format klasik yang diinginkan
Rekonstruksi setiap nilai ekspektasi Pauli menggunakan observable terpetakan dan urutan eksperimen yang sama dengan saat pembuatan. Lalu rata-ratakan nilai pertama untuk dan nilai sisanya untuk . Untuk circuit yang tidak dipotong, hitung nilai ekspektasi langsung dari hasil hitung pengukuran.
cut_expvals = reconstruct_expectation_values(
cut_result, coefficients, mapped_observables.paulis
)
uncut_counts = uncut_result[0].data.meas.get_counts()
uncut_expvals = [
sampled_expectation_value(uncut_counts, p) for p in observables.paulis
]
def compare_results(cut_expvals, uncut_expvals, num_qubits):
"""Display average Z and periodic ZZ values against the ideal values."""
cut_averages = np.asarray(cut_expvals).reshape(2, num_qubits).mean(axis=1)
uncut_averages = (
np.asarray(uncut_expvals).reshape(2, num_qubits).mean(axis=1)
)
print("Cut averages (Z, ZZ):", cut_averages)
print("Uncut averages (Z, ZZ):", uncut_averages)
print("Absolute cut errors:", np.abs(cut_averages - [-1, 1]))
print("Absolute uncut errors:", np.abs(uncut_averages - [-1, 1]))
fig, ax = plt.subplots(figsize=(7, 4))
positions = np.arange(2)
for offset, values, label, color in [
(-0.25, [-1, 1], "Ideal", "#4589ff"),
(0, cut_averages, "Cut", "#a56eff"),
(0.25, uncut_averages, "Uncut", "#009d9a"),
]:
ax.bar(
positions + offset, values, width=0.25, label=label, color=color
)
ax.set_xticks(positions, ["Average Z", "Average periodic ZZ"])
ax.set_ylabel("Expectation value")
ax.axhline(0, color="black", linewidth=0.8)
ax.legend()
fig.tight_layout()
plt.show()
compare_results(cut_expvals, uncut_expvals, num_qubits)
Cut averages (Z, ZZ): [-1.00100833 0.99778333]
Uncut averages (Z, ZZ): [-1. 1.]
Absolute cut errors: [0.00100833 0.00221667]
Absolute uncut errors: [0. 0.]
Hasil tanpa noise seharusnya mendekati nilai ideal. Rekonstruksi dengan shot terbatas bisa berfluktuasi, dan estimasi hasil rekonstruksi bisa berada di luar . Pemotongan tidak memperbaiki hasil yang sudah bebas noise; contoh ini memverifikasi dekomposisi, pemetaan layout, dan rekonstruksi sebelum dijalankan di hardware.
Contoh hardware skala besar
Pakai ulang alur kerja ini pada QPU operasional yang paling tidak sibuk dengan minimal 120 qubit. Pilih jalur terpendek terpanjang di graf coupling tak berarahnya: setiap pasangan berurutan saling terhubung, sedangkan kedua ujungnya berjauhan. Ini menyesuaikan rantai dengan perangkat terpilih, bukan mengandalkan layout Eagle yang tetap. Kode memeriksa bahwa rantai berisi minimal 30 qubit dan bahwa kedua ujungnya bukan tetangga.
Blok berikut menggabungkan keempat langkah. Gate twirling, measurement twirling, dan dynamical decoupling dinonaktifkan untuk kedua versi. Kedua job berjalan dalam batch yang sama dan diberi tag tutorial TUT_CCPBC. Eksperimen potongan tetap memakai total shot lebih banyak, jadi bandingkan akurasi sekaligus penggunaan QPU.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=120
)
# ------------------------- Step 1 -------------------------
graph = backend.coupling_map.graph.to_undirected(multigraph=False)
paths = rx.all_pairs_dijkstra_shortest_paths(graph, lambda _: 1)
initial_layout = list(
max(
(
path
for source_paths in paths.values()
for path in source_paths.values()
),
key=len,
)
)
num_qubits = len(initial_layout)
if num_qubits < 30:
raise ValueError(
"Select a backend with a shortest path of at least 30 qubits."
)
if graph.has_edge(initial_layout[0], initial_layout[-1]):
raise ValueError("The chain endpoints must not be directly connected.")
print(f"Backend: {backend.name}; chain length: {num_qubits}")
print("Initial layout:", initial_layout)
circuit, observables = build_periodic_problem(num_qubits)
# ------------------------- Step 2 -------------------------
isa_experiments, coefficients, mapped_observables = prepare_cut_experiments(
circuit, observables, backend, initial_layout
)
uncut_pm = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
initial_layout=initial_layout,
seed_transpiler=1234,
)
isa_uncut = uncut_pm.run(circuit.measure_all(inplace=False))
print("Uncut circuit depth:", isa_uncut.depth())
print("Cut circuit depths:", sorted({qc.depth() for qc in isa_experiments}))
# ------------------------- Step 3 -------------------------
options = SamplerOptions()
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False
options.environment.job_tags = ["TUT_CCPBC"]
shots = 10_000
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch, options=options)
cut_job = sampler.run(isa_experiments, shots=shots)
uncut_job = sampler.run([isa_uncut], shots=shots)
print("Cut job ID:", cut_job.job_id())
print("Uncut job ID:", uncut_job.job_id())
cut_result = cut_job.result()
uncut_result = uncut_job.result()
# ------------------------- Step 4 -------------------------
cut_expvals = reconstruct_expectation_values(
cut_result, coefficients, mapped_observables.paulis
)
uncut_counts = uncut_result[0].data.meas.get_counts()
uncut_expvals = [
sampled_expectation_value(uncut_counts, p) for p in observables.paulis
]
compare_results(cut_expvals, uncut_expvals, num_qubits)
qpu_seconds = sum(
job.metrics()["usage"]["quantum_seconds"] for job in [cut_job, uncut_job]
)
print(
f"Total QPU usage: {qpu_seconds:.1f} seconds ({qpu_seconds / 60:.2f} minutes)"
)
Backend: ibm_boston; chain length: 33
Initial layout: [0, 1, 2, 3, 16, 23, 24, 25, 37, 45, 44, 43, 56, 63, 64, 65, 77, 85, 86, 87, 97, 107, 106, 105, 117, 125, 124, 123, 136, 143, 142, 141, 140]
Boundary gates cut: 2
Number of experiments: 36
Sampling overhead: 81.0
Uncut circuit depth: 375
Cut circuit depths: [20]
Cut job ID: dagp7fj9k43c73admp40
Uncut job ID: dagp7fj9k43c73admp5g
Cut averages (Z, ZZ): [-0.92305152 0.89586667]
Uncut averages (Z, ZZ): [-0.61106061 0.51606061]
Absolute cut errors: [0.07694848 0.10413333]
Absolute uncut errors: [0.38893939 0.48393939]
Total QPU usage: 102.0 seconds (1.70 minutes)
Pada eksekusi yang tercatat di ibm_boston, estimasi hasil pemotongan kira-kira dan , dibandingkan dengan dan untuk circuit yang tidak dipotong. Kedua estimasi hasil pemotongan lebih dekat ke nilai ideal dan .
Bandingkan setiap hasil dengan nilai idealnya dan periksa kedalaman circuit serta total penggunaan QPU. Pemotongan menghilangkan kebutuhan routing gate periodik, tetapi menambah operasi perantara dan ketidakpastian statistik tambahan. Noise hardware dan kalibrasi memengaruhi perbandingan; perbaikan tidak dijamin. Diperlukan eksekusi berulang untuk menilai apakah selisih yang teramati signifikan secara statistik.
Langkah selanjutnya
-
Jelajahi circuit cutting untuk pengurangan kedalaman untuk interaksi nonlokal lainnya.
-
Coba wire cutting untuk estimasi nilai ekspektasi untuk membagi circuit menjadi subsistem yang lebih kecil.
-
Baca materi penjelasan circuit cutting untuk memahami tradeoff antara pemotongan dan overhead sampling.