מודל איזינג בשדה רוחבי עם ניהול ביצועים של Q-CTRL
הערכת שימוש: 2 דקות על מעבד Heron r2. (הערה: זוהי הערכה ב לבד. זמן הריצה שלך עשוי להשתנות.)
רקע
מודל איזינג בשדה רוחבי (TFIM) חשוב לחקר מגנטיות קוונטית ומעברי פאזה. הוא מתאר קבוצה של ספינים המסודרים בסריג, כאשר כל ספין מקיים אינטראקציה עם שכניו תוך שהוא מושפע גם משדה מגנטי חיצוני המניע תנודות קוונטיות.
גישה נפוצה לסימולציה של מודל זה היא שימוש בפירוק טרוטר כדי לקרב את אופרטור האבולוציה בזמן, תוך בניית מעגלים המחליפים בין סיבובים של קיוביט בודד ואינטראקציות שזירה של שני קיוביטים. עם זאת, סימולציה זו על חומרה אמיתית היא מאתגרת בגלל רעש ודה-קוהרנטיות, מה שמוביל לסטיות מהדינמיקה האמיתית. כדי להתגבר על זה, אנו משתמשים בכלי דיכוי שגיאות וניהול ביצועים Fire Opal של Q-CTRL, המוצעים כפונקציית Qiskit (ראה תיעוד Fire Opal). Fire Opal מבצע אופטימיזציה אוטומטית של ביצוע המעגל על ידי יישום ניתוק דינמי, פריסה מתקדמת, ניתוב וטכניקות דיכוי שגיאות נוספות, כולן מכוונות להפחתת רעש. עם שיפורים אלה, תוצאות החומרה מתיישרות בצורה הדוקה יותר עם סימולציות ללא רעש, ובכך אנו יכולים לחקור את דינמיקת המגנטיזציה של TFIM בנאמנות גבוהה יותר.
במדריך זה נבצע:
- בניית ההמילטוניאן TFIM על גרף של משולשי ספין מחוברים
- סימולציה של אבולוציית זמן עם מעגלים מטרוטרים בעומקים שונים
- חישוב והצגה חזותית של מגנטיזציות קיוביט בודד לאורך זמן
- השוואת סימולציות בסיס עם תוצאות מריצות חומרה באמצעות ניהול ביצועים Fire Opal של Q-CTRL
סקירה כללית
מודל איזינג בשדה רוחבי (TFIM) הוא מודל ספין קוונטי התופס מאפיינים חיוניים של מעברי פאזה קוונטיים. ההמילטוניאן מוגדר כך:
כאשר ו- הם אופרטורי Pauli הפועלים על קיוביט , הוא חוזק הצימוד בין ספינים שכנים, ו- הוא חוזק השדה המגנט י הרוחבי. האיבר הראשון מייצג אינטראקציות פרומגנטיות קלאסיות, בעוד שהאיבר השני מציג תנודות קוונטיות דרך השדה הרוחבי. כדי לדמות את הדינמיקה של TFIM, משתמשים בפירוק טרוטר של אופרטור האבולוציה האוניטרי , המיושם דרך שכבות של שערי RX ו-RZZ המבוססים על גרף מותאם אישית של משולשי ספין מחוברים. הסימולציה חוקרת כיצד המגנטיזציה מתפתחת עם הגדלת צעדי טרוטר.
הביצועים של יישום ה-TFIM המוצע מוערכים על ידי השוואת סימולציות ללא רעש עם backends רועשים. תכונות הביצוע המשופר ודיכוי השגיאות של Fire Opal משמשות למיתון השפעת הרעש בחומרה אמיתית, ומניבות הערכות אמינות יותר של משתני ספין ניתנים למדידה כמו וקורלטורים .
דרישות
לפני תחילת המדריך, וודא שהרכיבים הבאים מותקנים:
- Qiskit SDK v1.4 ואילך, עם תמיכה ב-הצגה ויזואלית
- Qiskit Runtime v0.40 ואילך (
pip install qiskit-ibm-runtime) - Qiskit Functions Catalog v0.9.0 (
pip install qiskit-ibm-catalog) - Fire Opal SDK v9.0.2 ואילך (
pip install fire-opal) - Q-CTRL Visualizer v8.0.2 ואילך (
pip install qctrl-visualizer)
הגדרה
ראשית, התאמת זהות באמצעות מפתח API של IBM Quantum. לאחר מכן, בחר את פונקציית Qiskit כדלקמן. (קוד זה מניח שכבר שמרת את החשבון שלך לסביבה המקומית שלך.)
# 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")
שלב 1: מיפוי קלטים קלאסיים לבעיה קוונטית
יצירת גרף TFIM
אנו מתחילים בהגדרת סריג הספינים והצימודים ביניהם. במדריך זה, הסריג בנוי ממשולשים מחוברים המסודרים בשרשרת ליניארית. כל משולש מורכב משלושה צמתים מחוברים בלולאה סגורה, והשרשרת נוצרת על ידי קישור צומת אחד של כל משולש למשולש הקודם.
פונקציית העזר connected_triangles_adj_matrix בונה את מטריצת הסמיכות למבנה זה. עבור שרשרת של משולשים, הגרף המתקבל מכיל צמתים.
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
כדי להציג ויזואלית את הסריג שהגדרנו זה עתה, אנו יכולים לשרטט את שרשרת המשולשים המחוברים ולתייג כל צומת. הפונקציה שלהלן בונה את הגרף עבור מספר נבחר של משולשים ומציגה אותו.
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
עבור מדריך זה נשתמש בשרשרת של 20 משולשים.
n_triangles = 20
n_qubits = 2 * n_triangles + 1
plot_triangle_chain(n_triangles, side=1.0)
plt.show()

צביעת קצוות הגרף
כ די ליישם את צימוד הספין-ספין, שימושי לקבץ קצוות שאינם חופפים. זה מאפשר לנו ליישם שערים של שני קיוביטים במקביל. אנו יכולים לעשות זאת באמצעות נוהל פשוט של צביעת קצוות [1], המקצה צבע לכל קצה כך שקצוות הנפגשים באותו צומת ממוקמים בקבוצות שונות.
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())
שלב 2: אופטימיזציה של הבעיה לביצוע על חומרה קוונטית
יצירת מעגלים מטרוטרים על גרפי ספין
כדי לדמות את הדינמיקה של ה-TFIM, אנו בונים מעגלים המקרבים את אופרטור האבולוציה בזמן.
אנו משתמשים בפירוק טרוטר מסדר שני:
כאשר ו-.
- איבר ה- מיושם עם שכבות של סיבובי
RX. - איבר ה- מיושם עם שכבות של שערי
RZZלאורך קצוות גרף האינטראקציה.
הזוויות של שערים אלה נקבעות על ידי השדה הרוחבי , קבוע הצימוד , וצעד הזמן . על ידי ערימת מספר צעדי טרוטר, אנו מייצרים מעגלים של עומק הולך וגדל המקרבים את הדינמיקה של המערכת. הפונקציות generate_tfim_circ_custom_graph ו-trotter_circuits בונות מעגל קוונטי מטרוטר מגרף אינטראקציית ספין שרירותי.
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
הערכת מגנטיזציות קיוביט בודד
כדי לחקור את הדינמיקה של המודל, אנו רוצים למדוד את המגנטיזציה של כל קיוביט, המוגדרת על ידי ערך התוחלת .
בסימולציות, אנו יכולים לחשב זאת ישירות מתוצאות המדידה. הפונקציה z_expectation מעבדת את ספירת מחרוזות הביטים ומחזירה את הערך של עבור אינדקס קיוביט נבחר. על חומרה אמיתית, אנו מעריכים את אותו כמות על ידי ציון אופרטור Pauli באמצעות הפונקציה generate_z_observables, ולאחר מכן ה-backend מחשב את ערך התוחלת.
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)
כעת אנו מגדירים את הפרמטרים ליצירת המעגלים המטרוטרים. במדריך זה, הסריג הוא שרשרת של 20 משולשים מחוברים, המתאימה למערכת של 41 קיוביטים.
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
שלב 3: ביצוע באמצעות primitives של Qiskit
הרצת סימולציית MPS
רשימת המעגלים המטרוטרים מבוצעת באמצעות סימולטור matrix_product_state עם בחירה שרירותית של shots. שיטת MPS מספקת קירוב יעיל של דינמיקת המעגל, כאשר הדיוק נקבע על ידי ממד הקשר הנבחר. עבור גדלי המערכת הנבחנים כאן, ממד הקשר ברירת המחדל מספיק כדי לתפוס את דינמיקת המגנטיזציה בנאמנות גבוהה. הספירות הגולמיות מנורמלות, ומהן אנו מחשבים את ערכי התוחלת של קיוביט בודד בכל צעד טרוטר. לבסוף, אנו מחשבים את הממוצע על פני כל הקיוביטים כדי לקבל עקומה אחת המראה כיצד המגנטיזציה משתנה לאורך זמן.
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)
הרצה על חומרה
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)
הרצה על חומרה עם Fire Opal
אנו מעריכים את דינמיקת המגנטיזציה על חומרה קוונטית אמיתית. Fire Opal מספק פונקציית Qiskit המרחיבה את ה-primitive של Qiskit Runtime Estimator הסטנדרטי עם דיכוי שגיאות אוטומטי וניהול ביצועים. אנו שולחים את המעגלים המטרוטרים ישירות ל-backend של IBM® בעוד Fire Opal מטפל בביצוע המודע לרעש.
אנו מכינים רשימה של pubs, כאשר כל פריט מכיל מעגל ואת משתני Pauli-Z המתאימים. אלה מועברים לפונקציית ה-estimator של Fire Opal, המחזירה את ערכי התוחלת עבור כל קיוביט בכל צ עד טרוטר. לאחר מכן ניתן לחשב את הממוצע של התוצאות על פני הקיוביטים כדי לקבל את עקומת המגנטיזציה מהחומרה.
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()
שלב 4: עיבוד לאחר והחזרת תוצאה בפורמט קלאסי רצוי
לבסוף, אנו משווים את עקומת המגנטיזציה מהסימולטור עם התוצאות שהתקבלו על חומרה אמיתית. שרטוט שני העקומות זו לצד זו מראה כמה קרוב ביצוע החומרה עם Fire Opal מתאים לבסיס ללא רעש על פני צעדי טרוטר.
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,
)

אסמכתאות
[1] Graph coloring. Wikipedia. Retrieved September 15, 2025, from https://en.wikipedia.org/wiki/Graph_coloring
סקר מדריך
אנא הקדש דקה למתן משוב על מדריך זה. התובנות שלך יעזרו לנו לשפר את הצעות התוכן שלנו וחווית המשתמש.