Lewati ke konten utama

Model Ising Medan Transversal dengan Manajemen Performa Q-CTRL

Estimasi penggunaan: 2 menit pada prosesor Heron r2. (CATATAN: Ini hanya perkiraan. Waktu aktual bisa berbeda.)

Latar Belakang

Model Ising Medan Transversal (TFIM) penting untuk mempelajari kemagnetan kuantum dan transisi fase. Model ini menggambarkan sekumpulan spin yang tersusun pada kisi, di mana setiap spin berinteraksi dengan tetangganya sekaligus dipengaruhi oleh medan magnet eksternal yang mendorong fluktuasi kuantum.

Pendekatan umum untuk mensimulasikan model ini adalah menggunakan dekomposisi Trotter untuk mendekati operator evolusi waktu, dengan membangun sirkuit yang bergantian antara rotasi qubit tunggal dan interaksi dua qubit yang saling terjerat. Namun, simulasi ini pada perangkat keras nyata cukup menantang karena noise dan dekoherensi, yang menyebabkan penyimpangan dari dinamika sesungguhnya. Untuk mengatasinya, kita menggunakan alat penekanan error dan manajemen performa Fire Opal dari Q-CTRL, yang ditawarkan sebagai Qiskit Function (lihat dokumentasi Fire Opal). Fire Opal secara otomatis mengoptimalkan eksekusi sirkuit dengan menerapkan dynamical decoupling, tata letak canggih, routing, dan teknik penekanan error lainnya—semuanya bertujuan mengurangi noise. Dengan peningkatan ini, hasil perangkat keras lebih mendekati simulasi tanpa noise, sehingga kita dapat mempelajari dinamika magnetisasi TFIM dengan fidelitas lebih tinggi.

Dalam tutorial ini kita akan:

  • Membangun Hamiltonian TFIM pada graf segitiga spin yang terhubung
  • Mensimulasikan evolusi waktu dengan sirkuit Trotterisasi pada berbagai kedalaman
  • Menghitung dan memvisualisasikan magnetisasi qubit tunggal Zi\langle Z_i \rangle terhadap waktu
  • Membandingkan simulasi dasar dengan hasil eksekusi perangkat keras menggunakan manajemen performa Fire Opal dari Q-CTRL

Ikhtisar

Model Ising Medan Transversal (TFIM) adalah model spin kuantum yang menangkap fitur-fitur esensial dari transisi fase kuantum. Hamiltoniannya didefinisikan sebagai:

H=JiZiZi+1hiXiH = -J \sum_{i} Z_i Z_{i+1} - h \sum_{i} X_i

di mana ZiZ_i dan XiX_i adalah operator Pauli yang bekerja pada Qubit ii, JJ adalah kekuatan kopling antar spin yang bertetangga, dan hh adalah kekuatan medan magnet transversal. Suku pertama merepresentasikan interaksi feromagnetik klasik, sementara suku kedua memperkenalkan fluktuasi kuantum melalui medan transversal. Untuk mensimulasikan dinamika TFIM, kita menggunakan dekomposisi Trotter dari operator evolusi uniter eiHte^{-iHt}, yang diimplementasikan melalui lapisan gate RX dan RZZ berdasarkan graf kustom segitiga spin yang terhubung. Simulasi ini mengeksplorasi bagaimana magnetisasi Z\langle Z \rangle berevolusi seiring bertambahnya langkah Trotter.

Performa implementasi TFIM yang diusulkan dinilai dengan membandingkan simulasi tanpa noise dengan backend yang bernoise. Fitur eksekusi yang ditingkatkan dan penekanan error dari Fire Opal digunakan untuk memitigasi efek noise pada perangkat keras nyata, menghasilkan estimasi yang lebih andal untuk observabel spin seperti Zi\langle Z_i \rangle dan korelator ZiZj\langle Z_i Z_j \rangle.

Persyaratan

Sebelum memulai tutorial ini, pastikan kamu sudah menginstal hal-hal berikut:

  • Qiskit SDK v1.4 atau lebih baru, dengan dukungan visualisasi
  • Qiskit Runtime v0.40 atau lebih baru (pip install qiskit-ibm-runtime)
  • Qiskit Functions Catalog v0.9.0 (pip install qiskit-ibm-catalog)
  • Fire Opal SDK v9.0.2 atau lebih baru (pip install fire-opal)
  • Q-CTRL Visualizer v8.0.2 atau lebih baru (pip install qctrl-visualizer)

Pengaturan

Pertama, autentikasi menggunakan API key IBM Quantum-mu. Kemudian, pilih Qiskit Function sebagai berikut. (Kode ini mengasumsikan kamu sudah menyimpan akunmu ke lingkungan lokal.)

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib networkx numpy qctrlvisualizer qiskit qiskit-aer qiskit-ibm-catalog qiskit-ibm-runtime
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit import QuantumCircuit
from qiskit_ibm_catalog import QiskitFunctionsCatalog
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit.quantum_info import SparsePauliOp
from qiskit_aer import AerSimulator

import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import qctrlvisualizer as qv
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Access Function
perf_mgmt = catalog.load("q-ctrl/performance-management")

Langkah 1: Petakan input klasik ke masalah kuantum

Buat graf TFIM

Kita mulai dengan mendefinisikan kisi spin dan kopling di antara spin tersebut. Dalam tutorial ini, kisi dibangun dari segitiga-segitiga yang terhubung dan tersusun dalam rantai linier. Setiap segitiga terdiri dari tiga simpul yang terhubung dalam lingkaran tertutup, dan rantai dibentuk dengan menghubungkan satu simpul dari setiap segitiga ke segitiga sebelumnya.

Fungsi pembantu connected_triangles_adj_matrix membangun matriks adjacency untuk struktur ini. Untuk rantai nn segitiga, graf yang dihasilkan memiliki 2n+12n+1 simpul.

def connected_triangles_adj_matrix(n):
"""
Generate the adjacency matrix for 'n' connected triangles in a chain.
"""
num_nodes = 2 * n + 1
adj_matrix = np.zeros((num_nodes, num_nodes), dtype=int)

for i in range(n):
a, b, c = i * 2, i * 2 + 1, i * 2 + 2 # Nodes of the current triangle

# Connect the three nodes in a triangle
adj_matrix[a, b] = adj_matrix[b, a] = 1
adj_matrix[b, c] = adj_matrix[c, b] = 1
adj_matrix[a, c] = adj_matrix[c, a] = 1

# If not the first triangle, connect to the previous triangle
if i > 0:
adj_matrix[a, a - 1] = adj_matrix[a - 1, a] = 1

return adj_matrix

Untuk memvisualisasikan kisi yang baru kita definisikan, kita bisa memplot rantai segitiga yang terhubung dan memberi label pada setiap simpul. Fungsi di bawah ini membangun graf untuk jumlah segitiga yang dipilih dan menampilkannya.

def plot_triangle_chain(n, side=1.0):
"""
Plot a horizontal chain of n equilateral triangles.
Baseline: even nodes (0,2,4,...,2n) on y=0
Apexes: odd nodes (1,3,5,...,2n-1) above the midpoint.
"""
# Build graph
A = connected_triangles_adj_matrix(n)
G = nx.from_numpy_array(A)

h = np.sqrt(3) / 2 * side
pos = {}

# Place baseline nodes
for k in range(n + 1):
pos[2 * k] = (k * side, 0.0)

# Place apex nodes
for k in range(n):
x_left = pos[2 * k][0]
x_right = pos[2 * k + 2][0]
pos[2 * k + 1] = ((x_left + x_right) / 2, h)

# Draw
fig, ax = plt.subplots(figsize=(1.5 * n, 2.5))
nx.draw(
G,
pos,
ax=ax,
with_labels=True,
font_size=10,
font_color="white",
node_size=600,
node_color=qv.QCTRL_STYLE_COLORS[0],
edge_color="black",
width=2,
)
ax.set_aspect("equal")
ax.margins(0.2)
plt.show()

return G, pos

Untuk tutorial ini kita akan menggunakan rantai 20 segitiga.

n_triangles = 20
n_qubits = 2 * n_triangles + 1
plot_triangle_chain(n_triangles, side=1.0)
plt.show()

Output of the previous code cell

Pewarnaan sisi graf

Untuk mengimplementasikan kopling spin–spin, sangat berguna untuk mengelompokkan sisi-sisi yang tidak tumpang tindih. Ini memungkinkan kita menerapkan gate dua qubit secara paralel. Kita bisa melakukannya dengan prosedur pewarnaan sisi sederhana [1], yang memberikan warna pada setiap sisi sehingga sisi-sisi yang bertemu di simpul yang sama ditempatkan dalam kelompok berbeda.

def edge_coloring(graph):
"""
Takes a NetworkX graph and returns a list of lists where each inner list contains
the edges assigned the same color.
"""
line_graph = nx.line_graph(graph)
edge_colors = nx.coloring.greedy_color(line_graph)

color_groups = {}
for edge, color in edge_colors.items():
if color not in color_groups:
color_groups[color] = []
color_groups[color].append(edge)

return list(color_groups.values())

Langkah 2: Optimalkan masalah untuk eksekusi pada perangkat keras kuantum

Hasilkan Circuit Trotter pada graf spin

Untuk mensimulasikan dinamika TFIM, kita membangun Circuit yang mengaproksimasi operator evolusi waktu.

U(t)=eiHt,whereH=Ji,jZiZjhiXi.U(t) = e^{-i H t}, \quad \text{where} \quad H = -J \sum_{\langle i,j \rangle} Z_i Z_j - h \sum_i X_i .

Kita menggunakan dekomposisi Trotter orde kedua:

eiHΔteiHXΔt/2eiHZΔteiHXΔt/2,e^{-i H \Delta t} \approx e^{-i H_X \Delta t / 2}\, e^{-i H_Z \Delta t}\, e^{-i H_X \Delta t / 2},

di mana HX=hiXiH_X = -h \sum_i X_i dan HZ=Ji,jZiZjH_Z = -J \sum_{\langle i,j \rangle} Z_i Z_j.

  • Suku HXH_X diimplementasikan dengan lapisan rotasi RX.
  • Suku HZH_Z diimplementasikan dengan lapisan Gate RZZ sepanjang sisi-sisi graf interaksi.

Sudut-sudut Gate ini ditentukan oleh medan transversal hh, konstanta kopling JJ, dan langkah waktu Δt\Delta t. Dengan menumpuk beberapa langkah Trotter, kita menghasilkan Circuit dengan kedalaman yang semakin bertambah untuk mengaproksimasi dinamika sistem. Fungsi generate_tfim_circ_custom_graph dan trotter_circuits membangun Circuit kuantum Trotter dari graf interaksi spin sembarang.

def generate_tfim_circ_custom_graph(
steps, h, J, dt, psi0, graph: nx.graph.Graph, meas_basis="Z", mirror=False
):
"""
Generate a second order trotter of the form e^(a+b) ~ e^(b/2) e^a e^(b/2) for simulating a transverse field ising model:
e^{-i H t} where the Hamiltonian H = -J \\sum_i Z_i Z_{i+1} + h \\sum_i X_i.

steps: Number of trotter steps
theta_x: Angle for layer of X rotations
theta_zz: Angle for layer of ZZ rotations
theta_x: Angle for second layer of X rotations
J: Coupling between nearest neighbor spins
h: The transverse magnetic field strength
dt: t/total_steps
psi0: initial state (assumed to be prepared in the computational basis).
meas_basis: basis to measure all correlators in

This is a second order trotter of the form e^(a+b) ~ e^(b/2) e^a e^(b/2)
"""
theta_x = h * dt
theta_zz = -2 * J * dt
nq = graph.number_of_nodes()
color_edges = edge_coloring(graph)
circ = QuantumCircuit(nq, nq)
# Initial state, for typical cases in the computational basis
for i, b in enumerate(psi0):
if b == "1":
circ.x(i)
# Trotter steps
for step in range(steps):
for i in range(nq):
circ.rx(theta_x, i)
if mirror:
color_edges = [sublist[::-1] for sublist in color_edges[::-1]]
for edge_list in color_edges:
for edge in edge_list:
circ.rzz(theta_zz, edge[0], edge[1])
for i in range(nq):
circ.rx(theta_x, i)

# some typically used basis rotations
if meas_basis == "X":
for b in range(nq):
circ.h(b)
elif meas_basis == "Y":
for b in range(nq):
circ.sdg(b)
circ.h(b)

for i in range(nq):
circ.measure(i, i)

return circ

def trotter_circuits(G, d_ind_tot, J, h, dt, meas_basis, mirror=True):
"""
Generates a sequence of Trotterized circuits, each with increasing depth.
Given a spin interaction graph and Hamiltonian parameters, it constructs
a list of circuits with 1 to d_ind_tot Trotter steps

G: Graph defining spin interactions (edges = ZZ couplings)
d_ind_tot: Number of Trotter steps (maximum depth)
J: Coupling between nearest neighboring spins
h: Transverse magnetic field strength
dt: (t / total_steps
meas_basis: Basis to measure all correlators in
mirror: If True, mirror the Trotter layers
"""
qubit_count = len(G)
circuits = []
psi0 = "0" * qubit_count

for steps in range(1, d_ind_tot + 1):
circuits.append(
generate_tfim_circ_custom_graph(
steps, h, J, dt, psi0, G, meas_basis, mirror
)
)
return circuits

Estimasi magnetisasi single-Qubit Zi\langle Z_i \rangle

Untuk mempelajari dinamika model, kita ingin mengukur magnetisasi setiap Qubit, yang didefinisikan oleh nilai ekspektasi Zi=ψZiψ\langle Z_i \rangle = \langle \psi | Z_i | \psi \rangle.

Dalam simulasi, kita bisa menghitungnya langsung dari hasil pengukuran. Fungsi z_expectation memproses jumlah bitstring dan mengembalikan nilai Zi\langle Z_i \rangle untuk indeks Qubit yang dipilih. Pada perangkat keras nyata, kita mengevaluasi kuantitas yang sama dengan menentukan operator Pauli menggunakan fungsi generate_z_observables, lalu Backend akan menghitung nilai ekspektasinya.

def z_expectation(counts, index):
"""
counts: Dict of mitigated bitstrings.
index: Index i in the single operator expectation value < II...Z_i...I > to be calculated.
return: < Z_i >
"""
z_exp = 0
tot = 0
for bitstring, value in counts.items():
bit = int(bitstring[index])
sign = 1
if bit % 2 == 1:
sign = -1
z_exp += sign * value
tot += value

return z_exp / tot
def generate_z_observables(nq):
observables = []
for i in range(nq):
pauli_string = "".join(["Z" if j == i else "I" for j in range(nq)])
observables.append(SparsePauliOp(pauli_string))
return observables
observables = generate_z_observables(n_qubits)

Sekarang kita mendefinisikan parameter untuk menghasilkan Circuit Trotter. Dalam tutorial ini, kisi adalah rantai 20 segitiga terhubung, yang sesuai dengan sistem 41-Qubit.

all_circs_mirror = []
for num_triangles in [n_triangles]:
for meas_basis in ["Z"]:
A = connected_triangles_adj_matrix(num_triangles)
G = nx.from_numpy_array(A)
nq = len(G)
d_ind_tot = 22
dt = 2 * np.pi * 1 / 30 * 0.25
J = 1
h = -7
all_circs_mirror.extend(
trotter_circuits(G, d_ind_tot, J, h, dt, meas_basis, True)
)
circs = all_circs_mirror

Langkah 3: Eksekusi menggunakan primitif Qiskit

Jalankan simulasi MPS

Daftar Circuit Trotter dieksekusi menggunakan simulator matrix_product_state dengan pilihan arbitrer 40964096 shot. Metode MPS memberikan aproksimasi efisien dari dinamika Circuit, dengan akurasi yang ditentukan oleh dimensi ikatan yang dipilih. Untuk ukuran sistem yang dipertimbangkan di sini, dimensi ikatan default sudah cukup untuk menangkap dinamika magnetisasi dengan fidelitas tinggi. Jumlah mentah dinormalisasi, dan dari sini kita menghitung nilai ekspektasi single-Qubit Zi\langle Z_i \rangle pada setiap langkah Trotter. Akhirnya, kita menghitung rata-rata atas semua Qubit untuk mendapatkan satu kurva yang menunjukkan bagaimana magnetisasi berubah seiring waktu.

backend_sim = AerSimulator(method="matrix_product_state")

def normalize_counts(counts_list, shots):
new_counts_list = []
for counts in counts_list:
a = {k: v / shots for k, v in counts.items()}
new_counts_list.append(a)
return new_counts_list

def run_sim(circ_list):
shots = 4096
res = backend_sim.run(circ_list, shots=shots)
normed = normalize_counts(res.result().get_counts(), shots)
return normed

sim_counts = run_sim(circs)

Jalankan pada perangkat keras

service = QiskitRuntimeService()
backend = service.backend("ibm_marrakesh")

def run_qiskit(circ_list):
shots = 4096
pm = generate_preset_pass_manager(backend=backend)
isa_circuits = [pm.run(qc) for qc in circ_list]
sampler = Sampler(mode=backend)
res = sampler.run(isa_circuits, shots=shots)
res = [r.data.c.get_counts() for r in res.result()]
normed = normalize_counts(res, shots)
return normed

qiskit_counts = run_qiskit(circs)

Jalankan pada perangkat keras dengan Fire Opal

Kita mengevaluasi dinamika magnetisasi pada perangkat keras kuantum nyata. Fire Opal menyediakan Qiskit Function yang memperluas primitif Estimator Qiskit Runtime standar dengan penekanan kesalahan otomatis dan manajemen performa. Kita mengirimkan Circuit Trotter langsung ke Backend IBM® sementara Fire Opal menangani eksekusi yang peka terhadap noise.

Kita menyiapkan daftar pubs, di mana setiap item berisi sebuah Circuit dan observable Pauli-Z yang sesuai. Ini diteruskan ke fungsi estimator Fire Opal, yang mengembalikan nilai ekspektasi Zi\langle Z_i \rangle untuk setiap Qubit pada setiap langkah Trotter. Hasilnya kemudian bisa dirata-ratakan atas semua Qubit untuk mendapatkan kurva magnetisasi dari perangkat keras.

backend_name = "ibm_marrakesh"
estimator_pubs = [(qc, observables) for qc in all_circs_mirror[:]]

# Run the circuit using the estimator
qctrl_estimator_job = perf_mgmt.run(
primitive="estimator",
pubs=estimator_pubs,
backend_name=backend_name,
options={"default_shots": 4096},
)

result_qctrl = qctrl_estimator_job.result()

Langkah 4: Pasca-proses dan kembalikan hasil dalam format klasik yang diinginkan

Akhirnya, kita membandingkan kurva magnetisasi dari simulator dengan hasil yang diperoleh pada perangkat keras nyata. Memplot keduanya secara berdampingan menunjukkan seberapa dekat eksekusi perangkat keras dengan Fire Opal cocok dengan baseline tanpa noise di seluruh langkah Trotter.

def make_correlators(test_counts, nq, d_ind_tot):
mz = np.empty((nq, d_ind_tot))
for d_ind in range(d_ind_tot):
counts = test_counts[d_ind]
for i in range(nq):
mz[i, d_ind] = z_expectation(counts, i)
average_z = np.mean(mz, axis=0)
return np.concatenate((np.array([1]), average_z), axis=0)

sim_exp = make_correlators(sim_counts[0:22], nq=nq, d_ind_tot=22)
qiskit_exp = make_correlators(qiskit_counts[0:22], nq=nq, d_ind_tot=22)
qctrl_exp = [ev.data.evs for ev in result_qctrl[:]]
qctrl_exp_mean = np.concatenate(
(np.array([1]), np.mean(qctrl_exp, axis=1)), axis=0
)
def make_expectations_plot(
sim_z,
depths,
exp_qctrl=None,
exp_qctrl_error=None,
exp_qiskit=None,
exp_qiskit_error=None,
plot_from=0,
plot_upto=23,
):
import numpy as np
import matplotlib.pyplot as plt

depth_ticks = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

d = np.asarray(depths)[plot_from:plot_upto]
sim = np.asarray(sim_z)[plot_from:plot_upto]

qk = (
None
if exp_qiskit is None
else np.asarray(exp_qiskit)[plot_from:plot_upto]
)
qc = (
None
if exp_qctrl is None
else np.asarray(exp_qctrl)[plot_from:plot_upto]
)

qk_err = (
None
if exp_qiskit_error is None
else np.asarray(exp_qiskit_error)[plot_from:plot_upto]
)
qc_err = (
None
if exp_qctrl_error is None
else np.asarray(exp_qctrl_error)[plot_from:plot_upto]
)

# ---- helper(s) ----
def rmse(a, b):
if a is None or b is None:
return None
a = np.asarray(a, dtype=float)
b = np.asarray(b, dtype=float)
mask = np.isfinite(a) & np.isfinite(b)
if not np.any(mask):
return None
diff = a[mask] - b[mask]
return float(np.sqrt(np.mean(diff**2)))

def plot_panel(ax, method_y, method_err, color, label, band_color=None):
# Noiseless reference
ax.plot(d, sim, color="grey", label="Noiseless simulation")

# Method line + band
if method_y is not None:
ax.plot(d, method_y, color=color, label=label)
if method_err is not None:
lo = np.clip(method_y - method_err, -1.05, 1.05)
hi = np.clip(method_y + method_err, -1.05, 1.05)
ax.fill_between(
d,
lo,
hi,
alpha=0.18,
color=band_color if band_color else color,
label=f"{label} ± error",
)
else:
ax.text(
0.5,
0.5,
"No data",
transform=ax.transAxes,
ha="center",
va="center",
fontsize=10,
color="0.4",
)

# RMSE box (vs sim)
r = rmse(method_y, sim)
if r is not None:
ax.text(
0.98,
0.02,
f"RMSE: {r:.4f}",
transform=ax.transAxes,
va="bottom",
ha="right",
fontsize=8,
bbox=dict(
boxstyle="round,pad=0.35", fc="white", ec="0.7", alpha=0.9
),
)
# Axes
ax.set_xticks(depth_ticks)
ax.set_ylim(-1.05, 1.05)
ax.grid(True, which="both", linewidth=0.4, alpha=0.4)
ax.set_axisbelow(True)
ax.legend(prop={"size": 8}, loc="best")

fig, axes = plt.subplots(1, 2, figsize=(10, 4), dpi=300, sharey=True)

axes[0].set_title("Fire Opal (Q-CTRL)", fontsize=10)
plot_panel(
axes[0],
qc,
qc_err,
color="#680CE9",
label="Fire Opal",
band_color="#680CE9",
)
axes[0].set_xlabel("Trotter step")
axes[0].set_ylabel(r"$\langle Z \rangle$")
axes[1].set_title("Qiskit", fontsize=10)
plot_panel(
axes[1], qk, qk_err, color="blue", label="Qiskit", band_color="blue"
)
axes[1].set_xlabel("Trotter step")

plt.tight_layout()
plt.show()
depths = list(range(d_ind_tot + 1))
errors = np.abs(np.array(qctrl_exp_mean) - np.array(sim_exp))

errors_qiskit = np.abs(np.array(qiskit_exp) - np.array(sim_exp))
make_expectations_plot(
sim_exp,
depths,
exp_qctrl=qctrl_exp_mean,
exp_qctrl_error=errors,
exp_qiskit=qiskit_exp,
exp_qiskit_error=errors_qiskit,
)

Output of the previous code cell

Referensi

[1] Graph coloring. Wikipedia. Retrieved September 15, 2025, from https://en.wikipedia.org/wiki/Graph_coloring

Survei tutorial

Luangkan waktu sebentar untuk memberikan masukan tentang tutorial ini. Pendapat kamu akan membantu kami meningkatkan konten dan pengalaman pengguna.

Link to survey

Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.

Source: IBM Quantum docs — updated 15 Jan 2026
English version on doQumentation — updated 7 Mei 2026
This translation based on the English version of 9 Apr 2026