انتقل إلى المحتوى الرئيسي

محاكاة تشتت النيوترونات باستخدام سير عمل Serverless لديناميكيات AQC + Trotter

تقدير الاستخدام: 18 دقيقة على معالج Heron r3 (ملاحظة: هذا مجرد تقدير. قد يختلف وقت التشغيل الفعلي لديك.)

مخرجات التعلم

  • كيف يتوافق طيف تشتت النيوترونات غير المرن مع عامل البنية الديناميكية S(q,ω)S(q, \omega) لمغناطيس كمومي أحادي البعد.

  • كيفية تحضير حالة الأساس لـ KCuF3_3 (هايزنبرغ متماثل الخواص) باستخدام مجموعة إعادة تسوية مصفوفة الكثافة (DMRG) وتعظيم إخلاص حالة الجداء المصفوفي (MPS).

  • كيفية تشغيل تطور الزمن بطريقة Trotter، وضغط الدارة بواسطة الترجمة الكمومية التقريبية (AQC)، والتنفيذ المخفف للأخطاء (mitigated execution) كاستدعاء دالة واحد.

  • كيفية المعالجة اللاحقة لسلسلة زمنية σz(t)\langle \sigma_z \rangle(t) لكل موقع لتحويلها إلى S(q,ω)S(q, \omega) وتحديد متصل السبينونين الثنائي (two-spinon continuum).

المتطلبات الأساسية

الخلفية

يقيس تشتت النيوترونات غير المرن عامل البنية الديناميكية S(q,ω)S(q, \omega)، وهو تحويل فورييه الزمكاني لدالة الارتباط بين السبينات (spin-spin)، لذا فإن إعادة إنتاج S(q,ω)S(q, \omega) من نموذج سبين مجهري هو اختبار مباشر وقابل للتفنيد لمحاكاة كمومية. يدرس هذا البرنامج التعليمي KCuF3_3، وهي سلسلة هايزنبرغ مضادة للمغناطيسية ذات سبين-12\frac{1}{2}، إثاراتها ليست انقلابات سبين مفردة بل أزواج من السبينونات المجزأة (fractionalized spinons): فبدلًا من تشتت مغنون حاد، يُظهر S(q,ω)S(q, \omega) متصل سبينونين ثنائي عريض، محدود من الأسفل بـ π2sinq\tfrac{\pi}{2}|\sin q| ومن الأعلى بـ πsin(q/2)\pi|\sin(q/2)|. هذان هما المنحنيان المتقطعان في المخططات التالية. الفيزياء بالتفصيل، والمقارنة مقابل بيانات النيوترون المقاسة، مشروحة في البرنامج التعليمي الأصلي وفي Lee et al.، arXiv:2603.15608.

يعكس سير العمل الكمومي تجربة التشتت:

  1. حضّر حالة الأساس للسلسلة ψ0|\psi_0\rangle.

  2. اضربها باضطراب موضعي عند الموقع المركزي، وهو دوران ZZ بمقدار π/2\pi/2، يحاكي نقل الزخم والطاقة من النيوترون.

  3. طوّرها زمنيًا تحت هاملتوني هايزنبرغ، eiHte^{-iHt}، باستخدام صيغة جداء Trotter.

  4. قِس المغنطة لكل موقع σzj(t)\langle \sigma_z^j \rangle(t). كدالة للموقع jj والزمن tt، هذه هي بالضبط دالة غرين المتأخرة (retarded Green's function) GR(j,jc,t)G^R(j, j_c, t)، لذا لا حاجة لأي تحويل قبل تحويل فورييه في الخطوة 5.

  5. حوّل GRG^R بواسطة فورييه إلى S(q,ω)S(q, \omega).

يمكن أن تنشأ مشكلات في الخطوة 3، عندما تصبح دارات Trotter الدقيقة للتطورات الطويلة عميقة جدًا بالنسبة للعتاد. يعالج AQC مع الشبكات الموترية هذا الأمر عن طريق ضغط كتلة من خطوات Trotter في ansatz معلمي (parameterized) ثابت وضحل، يُعظَّم إخلاص حالته بالنسبة للتطور الدقيق كلاسيكيًا باستخدام محاكي MPS (arXiv:2301.08609). يحزم قالب ديناميكيات AQC هذه النواة الكمومية بأكملها (تركيب Trotter، وضغط AQC، والتنفيذ المخفف للأخطاء) خلف استدعاء واحد:

PRE (هذا الدفتر)FUNCTION (aqc-dynamics-function)POST (هذا الدفتر)
حالة الأساس من DMRG بالإضافة إلى تعظيم إخلاص MPS، مع دمج ضربة النيوترون في نفس الدارةتركيب Trotter → ضغط AQC → التنفيذ على statevector أو fake أو runtime، مع إعادة σzj(t)\langle \sigma_z^j \rangle(t) لكل موقعS(q,ω)S(q, \omega)، عامل البنية الديناميكية

يبقى العمل الخاص بالتجربة هنا في الدفتر: تحضير حالة الأساس (PRE) والمعالجة اللاحقة لـ S(q,ω)S(q, \omega) (POST). أما الخطوتان الكثيفتا الحوسبة الكمومية، الضغط والتنفيذ، فتُشغّلان داخل الدالة.

هذا البرنامج التعليمي مصاحب لـ محاكاة تشتت النيوترونات في المواد الكمومية باستخدام الدارات الكمومية، الذي يبني نفس التجربة بشكل مضمّن: نفس نموذج KCuF3_3، وتحضير حالة الأساس، وضربة النيوترون، والمعالجة اللاحقة، مع كتابة تركيب Trotter وضغط AQC والتنفيذ المخفف للأخطاء خطوة بخطوة. اقرأ ذلك البرنامج التعليمي لتتعلم كيف يعمل ضغط AQC. واقرأ هذا لتشغيل نفس التجربة عبر قالب دالة منشور: تصبح النواة الكمومية استدعاء دالة واحد، ويعمل ضغط AQC الذي يستغرق ساعات متعددة داخل عامل Serverless بدلًا من جهازك، لذا لست بحاجة إلى نظام حوسبة عالية الأداء (HPC) أو نواة مفتوحة أثناء تشغيله. يشغّل نفس الاستدعاء أيضًا تجارب ديناميكية أحادية البعد أخرى.

المتطلبات

قبل بدء هذا البرنامج التعليمي، تأكد من أن لديك ما يلي:

  • الدالة منشورة في حساب Qiskit Serverless الخاص بك. شغّل أولًا قالب الدالة المصاحب: نشر وتشغيل قالب دالة ديناميكيات AQC + Trotter. يشرح ذلك الدليل كيفية الحصول على ملفات المصدر وتحميل الدالة إلى حسابك. يستدعي هذا البرنامج التعليمي الدالة المنشورة فقط.

  • بيانات اعتماد IBM Quantum® محفوظة لـ QiskitServerless (راجع قالب الدالة). كلا المثالين في هذا البرنامج التعليمي يستدعيان الدالة المنشورة، لذا يحتاج كلاهما إليها.

  • Qiskit SDK الإصدار 2.0 أو أحدث (pip install qiskit).

  • عميل Qiskit IBM Catalog (pip install qiskit-ibm-catalog).

  • NumPy و SciPy و Matplotlib (pip install numpy scipy matplotlib). يلزم SciPy 1.14 أو أحدث لمُحسِّن COBYQA المستخدم في تحضير حالة الأساس.

  • حزمة الشبكات الموترية لـ AQC، لأن تحضير حالة الأساس في الخطوة 1 يعمل محليًا في هذا الدفتر: pip install 'qiskit-addon-aqc-tensor[quimb-jax]==0.3.1'.

ينتظر الاستدعاء الأول لدالة منشورة حديثًا حتى يقوم عامل Serverless بتثبيت تبعياته، لذا توقع زمن استجابة إضافيًا في ذلك التشغيل.

الإعداد

استورد المكتبات وعرّف الدوال المساعدة الخاصة بالتجربة المستخدمة لاحقًا: build_gs_ansatz (ansatz هاملتوني متغير، أو HVA، لتحضير حالة الأساس)، و prepare_ground_state (DMRG بالإضافة إلى تعظيم إخلاص MPS)، و get_spectrum و plot_green و plot_spectrum (المعالجة اللاحقة لـ S(q,ω)S(q, \omega)). هذه مُقتبسة من البرنامج التعليمي الأصلي لتشتت النيوترونات.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-aqc-tensor qiskit-ibm-catalog quimb scipy
from functools import partial

import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize

import quimb.tensor as qtn
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit
from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator
from qiskit_ibm_catalog import QiskitServerless
# Dynamical structure factor via discrete Fourier transform

def get_spectrum(n, Gjjc, dt, time_steps, q_steps, w_steps):
"""Compute the dynamical structure factor from the retarded Green's function.

Uses the center-site approximation and a discrete Fourier transform.
"""
green = Gjjc / 4 # sigma -> S=1/2
omega_max = np.pi / dt
qpoints = np.arange(0, 2 * np.pi, 2 * np.pi / q_steps)
omegas = np.arange(0, omega_max, omega_max / w_steps)
green_map = np.zeros((omegas.shape[0], qpoints.shape[0]))
center = n // 2 - 1
for iw, w in enumerate(omegas):
exponent = np.exp(1j * w * dt * np.arange(1, time_steps + 1))
S_w = np.dot(green.T, exponent) * dt
for iq, q in enumerate(qpoints):
q_matrix = np.exp(-1j * q * np.arange(-center, center + 2, 1))
green_map[iw, iq] = np.imag(np.dot(S_w, q_matrix))
return green_map

# Plotting helpers

def plot_spectrum(
dsf,
dt,
q_steps,
w_steps,
lower_bound=False,
upper_bound=False,
title=None,
):
"""Heat-map of the dynamical structure factor."""
omega_max = np.pi / dt
qpoints = np.arange(0, 2 * np.pi, 2 * np.pi / q_steps)
omegas = np.arange(0, omega_max, omega_max / w_steps)
x, y = np.meshgrid(qpoints, omegas)
fig, ax = plt.subplots(figsize=(8, 5))
c = ax.pcolormesh(x, y, dsf / np.max(dsf), cmap="viridis", shading="auto")
fig.colorbar(c, ax=ax, label="Normalized intensity")
if lower_bound:
ax.plot(
qpoints,
np.pi * np.abs(np.sin(qpoints)) / 2,
"--",
color="white",
lw=1.5,
label="Lower bound",
)
if upper_bound:
ax.plot(
qpoints,
np.pi * np.abs(np.sin(qpoints / 2)),
"--",
color="red",
lw=1.5,
label="Upper bound",
)
ax.set_ylim(0, 3.6)
ax.set_xlim(0, 2 * np.pi - 2 * np.pi / q_steps)
ax.set_xlabel(r"$q$", fontsize=16)
ax.set_ylabel(r"$\tilde{\omega} = \omega / J$", fontsize=16)
ax.set_xticks([0, np.pi / 2, np.pi, 3 * np.pi / 2, 2 * np.pi])
ax.set_xticklabels(["0", r"$\pi/2$", r"$\pi$", r"$3\pi/2$", r"$2\pi$"])
if lower_bound or upper_bound:
ax.legend(loc="upper right", fontsize=11)
if title:
ax.set_title(title, fontsize=14)
plt.tight_layout()
plt.show()

def plot_green(n, Gjjc, time_steps, dt, title=None):
"""Heat-map of the retarded Green's function in real space and time."""
fig, ax = plt.subplots(figsize=(8, 6))
t_axis = np.arange(1, time_steps + 1) * dt
site_axis = np.arange(n)
x, y = np.meshgrid(t_axis, site_axis)
c = ax.pcolormesh(
x,
y,
np.real(Gjjc).T,
cmap="RdBu",
vmax=0.5,
vmin=-0.5,
shading="auto",
)
fig.colorbar(c, ax=ax, label=r"Re $G^R(j, j_c, t)$")
ax.set_xlabel(r"Time ($t / J^{-1}$)", fontsize=16)
ax.set_ylabel("Site index $j$", fontsize=16)
if title:
ax.set_title(title, fontsize=14)
plt.tight_layout()
plt.show()

# Variational ground-state ansatz (HVA)

def _apply_xxz_pair_gate(qc, q0, q1, theta):
"""Apply the parameterized XXZ-type two-qubit gate used in the HVA."""
qc.cx(q0, q1)
qc.rz(theta, q1)
qc.h(q0)
qc.rz(theta + np.pi / 2, q0)
qc.cx(q0, q1)
qc.rz(-theta, q1)
qc.h(q1)
qc.cx(q1, q0)
qc.rz(np.pi / 2, q1)
qc.rz(-np.pi / 2, q0)
qc.h(q1)
qc.h(q0)

def build_gs_ansatz(n, params, layers):
"""Build the Hamiltonian variational ansatz (HVA) circuit for
ground-state preparation of the 1D Heisenberg model.

Starts from a product of singlet pairs and applies alternating
odd/even layers of parameterized XXZ gates. For layer r,
params[2 * r] is the odd-layer (inter-pair) angle and
params[2 * r + 1] is the even-layer (intra-pair) angle.
"""
qc = QuantumCircuit(n)
# Initial singlet product state
for i in range(n // 2):
qc.x(2 * i)
qc.x(2 * i + 1)
qc.h(2 * i + 1)
qc.cx(2 * i + 1, 2 * i)
# Variational layers
for r in range(layers):
for i in range(1, (n + 1) // 2): # odd layer
_apply_xxz_pair_gate(qc, 2 * i - 1, 2 * i, params[2 * r])
for i in range(n // 2): # even layer
_apply_xxz_pair_gate(qc, 2 * i, 2 * i + 1, params[2 * r + 1])
return qc

def prepare_ground_state(n, gs_layers=5, max_bond=128, cutoff=1e-8):
"""Prepare the KCuF3 (isotropic Heisenberg) ground state as a QuantumCircuit.

Runs DMRG (quimb MPO + DMRG2) to get the chain's ground state, then optimizes
the HVA angles to maximize the MPS overlap |<psi_ansatz|psi_DMRG>|^2. No exact
diagonalization, so it scales to larger n.
"""
J = Jz = 1.0
builder = qtn.SpinHam1D(S=1 / 2)
builder += J * 0.5, "+", "-"
builder += J * 0.5, "-", "+"
builder += Jz, "Z", "Z"
H_mpo = builder.build_mpo(L=n)
dmrg = qtn.DMRG2(H_mpo)
dmrg.solve(tol=1e-8, verbosity=0)

gs_sim = QuimbSimulator(
quimb_circuit_factory=partial(
qtn.CircuitMPS, gate_opts=dict(cutoff=cutoff, max_bond=max_bond)
),
autodiff_backend="jax",
)

def gs_infidelity(params):
psi = tensornetwork_from_circuit(
build_gs_ansatz(n, params, gs_layers), gs_sim
).psi
return 1 - abs(psi.H @ dmrg.state) ** 2

# Seed and optimizer match the original tutorial. Each layer starts at
# [0, pi/2]: an odd-layer angle of 0 makes the inter-pair gate the identity,
# and an even-layer angle of pi/2 makes the intra-pair gate a SWAP (since
# 0.5 * (XX + YY + ZZ) = SWAP - I/2). That puts the seed at the singlet-pair
# product limit, which is already a decent approximation to the Heisenberg
# ground state, so the optimizer only has to refine it. The small jitter
# (fixed RNG seed, so runs are reproducible) breaks the exact symmetry
# between layers; COBYQA then runs for up to 100 iterations.
rng = np.random.default_rng(12345)
x0 = np.tile([0.0, np.pi / 2], gs_layers) + rng.normal(
scale=0.1, size=2 * gs_layers
)
result_gs = scipy.optimize.minimize(
gs_infidelity, x0, method="COBYQA", options={"maxiter": 100}
)
print(f"DMRG ground-state energy: {dmrg.energy:.6f}")
print(f"GS fidelity: {1 - result_gs.fun:.4f}")
return build_gs_ansatz(n, result_gs.x, gs_layers)

print("Setup complete - helpers defined.")
Setup complete - helpers defined.

تحميل قالب الدالة

اتصل بـ Qiskit Serverless وحمّل aqc-dynamics-function المنشورة. يستدعي كلا المثالين في هذا البرنامج التعليمي نفس المقبض (handle) fn، لذا تُحمَّل الدالة مرة واحدة، هنا.

# Credentials are read from the account saved once via QiskitServerless.save_account(...)
serverless = QiskitServerless()
fn = serverless.load("aqc-dynamics-function")

مثال محاكي صغير النطاق

نشغّل أولًا سير العمل الكامل على سلسلة صغيرة مكونة من 10 مواقع باستخدام Backend الدقيق statevector. يُتحقق هذا من صحة خط الأنابيب PRE → FUNCTION → POST قبل إنفاق أي وقت من QPU.

الخطوة 1: تحويل المدخلات الكلاسيكية إلى مشكلة كمومية

ابنِ هاملتوني KCuF3_3 كـ SparsePauliOp (هايزنبرغ متماثل الخواص: XX+YY+ZZXX + YY + ZZ باقتران 14\tfrac14 على كل رابطة بين أقرب الجيران؛ السلاسل هي مؤثرات باولي، لذا يعطي 14\tfrac14 اقتران سبين-12\frac{1}{2}). حضّر حالة الأساس باستخدام DMRG بالإضافة إلى تعظيم إخلاص MPS، ثم أدمج ضربة النيوترون: دوران ZZ بمقدار π/2\pi/2 عند الموقع المركزي. الدارة المُحضَّرة هي ما نسلمه إلى الدالة كـ initial_state. نترك observables عند قيمتها الافتراضية (ZZ لكل موقع)، وهي بالضبط قراءة σzj(t)\langle \sigma_z^j \rangle(t) التي يحتاجها سير عمل النيوترون.

n = 10
dt = 0.6 # physical time per Trotter step (also the omega-axis unit in POST)
time_steps = 10
center = n // 2 - 1

# MPS-simulator settings, shared by the ground-state prep here and the AQC
# compression inside the function (matches the original tutorial).
mps_max_bond = 32
mps_cutoff = 1e-8

# 1D isotropic Heisenberg (KCuF3) Hamiltonian on n qubits
H = SparsePauliOp.from_sparse_list(
[(p, [i, i + 1], 0.25) for i in range(n - 1) for p in ("XX", "YY", "ZZ")],
num_qubits=n,
)

# Ground state (DMRG + fidelity max) + neutron kick baked into the same circuit
gs_circuit = prepare_ground_state(
n, gs_layers=3, max_bond=mps_max_bond, cutoff=mps_cutoff
)
gs_circuit.rz(
np.pi / 2, center
) # exp(-i (pi/2)/2 Z_center): the neutron perturbation
print(
f"Prepared {n}-qubit ground state with the neutron kick at site {center}."
)
DMRG ground-state energy: -4.258035
GS fidelity: 0.9841
Prepared 10-qubit ground state with the neutron kick at site 4.

الخطوتان 2 و3: الضغط والتنفيذ باستخدام قالب الدالة

في سير عمل مكتوب يدويًا، هاتان مرحلتان منفصلتان: تحسين الدارات للعتاد (الخطوة 2) وتنفيذها (الخطوة 3). يدمج قالب الدالة كليهما في استدعاء واحد. فهو ينفذ تركيب Trotter، وضغط AQC، وترجمة العتاد (transpilation)، ثم يشغّل الدارات (هنا على المحاكي الدقيق، ولاحقًا مع تخفيف مدمج للأخطاء على العتاد). المعاملان القابلان للضبط هما aqc_segments (خطة الضغط) و aqc_options (إعدادات MPS والمُحسِّن). كل جزء {"n_steps": k, "ansatz_steps": m} يضغط k من خطوات Trotter المتتالية إلى ansatz مبني من هدف Trotter بـ m خطوة، وأي خطوات تتجاوز sum(n_steps) تعمل كـ Trotter عادي. تُضغط الخطوات المبكرة ذات التشابك المنخفض جيدًا في ansatz ضحل (ansatz_steps=1)، لذا نضغط هنا الخطوات الثلاث الأولى في ansatz أحادي الطبقة والخطوتين التاليتين في ansatz أعمق ثنائي الطبقة؛ وتعمل الخطوات الخمس المتبقية من أصل 10 خطوات Trotter كـ Trotter عادي. بالنسبة لـ aqc_options نعكس البرنامج التعليمي الأصلي: بُعد رابطة MPS max_bond=32، وcutoff=1e-8، ومُحسِّن L-BFGS-B محدد بحد أقصى 100 تكرار.

استدعِ الدالة المُحمَّلة في الإعداد. يشغّل backend="statevector" المسار المرجعي الدقيق: بدون وقت QPU، حيث تعمل الدارات على محاكي statevector دقيق داخل عامل Serverless (لا يزال يلزم حساب Qiskit Serverless محفوظ لاستدعائها). يحمل initial_state حالة الأساس المُحضَّرة (بما في ذلك الضربة)؛ ويُحذف observables بحيث تقيس الدالة ZZ الافتراضي لكل موقع.

job = fn.run(
t_steps=time_steps,
aqc_segments=[
{
"n_steps": 3,
"ansatz_steps": 1,
}, # early steps -> shallow 1-layer ansatz
{
"n_steps": 2,
"ansatz_steps": 2,
}, # later steps -> deeper 2-layer ansatz
],
aqc_options={
"max_bond": mps_max_bond, # MPS bond dimension for AQC compression
"cutoff": mps_cutoff,
"optimizer_settings": {
"method": "L-BFGS-B",
"jac": True,
"options": {"maxiter": 100},
},
},
dt=dt,
hamiltonian=H,
initial_state=gs_circuit, # prepared ground state including the neutron kick
# observables omitted -> default per-site Z (the neutron sigma_z readout)
backend="statevector",
)
print(job.status()) # rerun this cell until status says DONE
DONE
# The per-site <sigma_z>(t) the function returns is the retarded Green's function
# G(j, j_c, t). The workflow samples t = 1..time_steps, so drop the t = 0 row (the
# prepared+kicked state before any evolution) before post-processing.
result = job.result()
print(
"AQC fidelities:",
{k: round(v, 4) for k, v in result["metadata"]["aqc_fidelities"].items()},
)

ev = np.array(result["expectation_values"])
Gjjc = ev[1:] # shape (time_steps, n)
print("Green's function shape:", Gjjc.shape)
AQC fidelities: {'1': 1.0, '2': 0.9999, '3': 0.9992, '4': 0.9998, '5': 0.9995}
Green's function shape: (10, 10)

الخطوة 4: المعالجة اللاحقة وإعادة النتيجة بالصيغة الكلاسيكية المطلوبة

حوّل دالة غرين بواسطة فورييه إلى S(q,ω)S(q, \omega)، وطبّق التماثل المرآتي (mirror-symmetrize)، واقطع القيم السالبة: هذه هي المعالجة اللاحقة القياسية للنيوترون. التماثل المرآتي دقيق لأن S(q,ω)=S(q,ω)S(q, \omega) = S(-q, \omega) لهذا النموذج، والقيم السالبة التي تبقى هي شوائب ناتجة عن تحويل فورييه لسلسلة زمنية محدودة ومُعاينة بشكل متقطع، لذا تُقطع إلى صفر. في هذا التشغيل الدقيق الصغير، لا يُحل متصل السبينونين الثنائي إلا بشكل تقريبي، لكن الآلية مطابقة لتشغيل العتاد الذي يلي.

q_res, w_res = 100, 100
spectrum = get_spectrum(n, Gjjc, dt, time_steps, q_res, w_res)
spectrum = -(spectrum + spectrum[:, ::-1]) / 2 # mirror symmetry
spectrum = np.clip(spectrum, a_min=0, a_max=None) # clip negatives

plot_green(
n,
Gjjc,
time_steps,
dt,
title=f"Retarded Green's function - {n} qubits (AQC, statevector)",
)
plot_spectrum(
spectrum,
dt,
q_res,
w_res,
lower_bound=True,
upper_bound=True,
title=f"Dynamical structure factor - {n} qubits (AQC, statevector)",
)

Output of the previous code cell

Output of the previous code cell

مثال عتاد واسع النطاق

يتوسع نفس سير العمل من دون تغيير أي جزء من كود العلوم: سلسلة مكونة من 30 موقعاً، وضعف عمق تروتر (20 خطوة)، وخطة ضغط تُغيّر عمق الأنساتز (أنساتز أعمق للخطوات المتأخرة الأكثر تشابكاً)، والتنفيذ على معالج IBM Quantum مع تخفيف الأخطاء المدمج في الدالة (فك الاقتران الديناميكي، وتمويه باولي، وإخماد خطأ القراءة المموّه (TREX)). سنستعرض نفس الخطوات الأربع التي مررنا بها في مثال المحاكي، معيدين استخدام مقبض fn من الإعداد.

مقياس صغيرمقياس كبير
الكيوبتات1030
خطوات تروتر1020
خطوات مضغوطة بـ AQC (طبقة واحدة + طبقتان)3 + 2 = 56 + 4 = 10
طبقات أنساتز الحالة الأرضية35
أقصى بُعد رابطة MPS32128
الخلفيةstatevectorQPU مع DD وتمويه باولي و TREX

الخطوة 1: تعيين المدخلات الكلاسيكية إلى مسألة كمومية

بناء نفس SparsePauliOp لهايزنبرغ الخاص بـ KCuF3_3 وتحضير الحالة الأرضية، والآن بأنساتز أعمق gs_layers=5 للسلسلة الأطول، ثم دمج ركلة النيوترون ZZ بمقدار π/2\pi/2 في الموقع المركزي. هذا مطابق تماماً للتعيين الصغير المقياس، لكن عند n=30n = 30.

توقع دقة حالة أرضية أقل من التشغيل ذي 10 مواقع: حوالي 0.82 هنا مقابل 0.98 للسلسلة الأصغر، لأن خمس طبقات HVA لا يمكنها التقاط حالة أرضية لسلسلة من 30 موقعاً بشكل كامل. هذا متوقع وليس فشلاً، والدليل التعليمي الأصلي يقبل حوالي 0.65 عند 50 موقعاً لنفس السبب. رفع gs_layers أو حد تكرارات COBYQA يحسّن ذلك، بتكلفة كلاسيكية إضافية.

n = 30
dt = 0.6
time_steps = 20
center = n // 2 - 1

# Same MPS settings as the original large-scale run: a larger bond for the
# longer, more-entangled chain (shared by GS prep and AQC compression).
mps_max_bond = 128
mps_cutoff = 1e-8

# Same KCuF3 Hamiltonian and ground-state prep, on a larger chain
H = SparsePauliOp.from_sparse_list(
[(p, [i, i + 1], 0.25) for i in range(n - 1) for p in ("XX", "YY", "ZZ")],
num_qubits=n,
)
gs_circuit = prepare_ground_state(
n, gs_layers=5, max_bond=mps_max_bond, cutoff=mps_cutoff
)
gs_circuit.rz(np.pi / 2, center) # neutron kick at the center site
print(
f"Prepared {n}-qubit ground state with the neutron kick at site {center}."
)
DMRG ground-state energy: -13.111355
GS fidelity: 0.8201
Prepared 30-qubit ground state with the neutron kick at site 14.

الخطوتان 2 و3: الضغط والتنفيذ باستخدام قالب الدالة

نفس الاستدعاء الفردي كما في مثال المحاكي، والآن مع توجيه backend_name إلى معالج IBM Quantum، بحيث تُترجم الدالة وتنفذ هناك. خطة الضغط تُغيّر عمق الأنساتز: تُضغط أول ست خطوات تروتر (منخفضة التشابك) إلى أنساتز بطبقة واحدة ضحلة، والأربع التالية إلى أنساتز أعمق بطبقتين، بينما تُنفذ بقية الخطوات العشر من أصل العشرين كتروتر عادي. aqc_options يرفع بُعد رابطة MPS إلى max_bond=128 للسلسلة الأطول والأكثر تشابكاً (مطابقة للأصل)، مع الإبقاء على نفس مُحسّن L-BFGS-B محدوداً بـ 100 تكرار. تُفعّل estimator_options تخفيف الأخطاء المدمج: فك الاقتران الديناميكي (XY4)، وتمويه البوابات، وتخفيف قياس TREX. الإعدادات الافتراضية للدالة تطابق بالفعل الدليل التعليمي الأصلي لكل هذه العناصر باستثناء ميزانية تعلم TREX (measure_noise_learning). لا تزال الكتلة كلها مكتوبة بالكامل لأن estimator_options المقدمة من المستدعي تستبدل الإعدادات الافتراضية للدالة بالكامل بدلاً من الدمج معها، لذا فإن حذف أي مفتاح سيعود إلى الإعداد الافتراضي لـ IBM Quantum Compute بدلاً من إعداد الدالة.

# Steps 2 + 3: the function compresses (varied ansatz) and executes on hardware.
job = fn.run(
t_steps=time_steps,
aqc_segments=[
{
"n_steps": 6,
"ansatz_steps": 1,
}, # early steps -> shallow 1-layer ansatz
{
"n_steps": 4,
"ansatz_steps": 2,
}, # later steps -> deeper 2-layer ansatz
],
aqc_options={
"max_bond": mps_max_bond, # 128 for the longer chain
"cutoff": mps_cutoff,
"optimizer_settings": {
"method": "L-BFGS-B",
"jac": True,
"options": {"maxiter": 100},
},
},
dt=dt,
hamiltonian=H,
initial_state=gs_circuit,
backend_name="ibm_pittsburgh",
# Mitigation settings from the original tutorial. Only the two
# measure_noise_learning values differ from the function's defaults; the rest
# restates them, because a caller-supplied estimator_options dict replaces the
# function's defaults wholesale rather than merging into them.
estimator_options={
"environment": {"job_tags": ["TUT-SNS"]},
"dynamical_decoupling": {"enable": True, "sequence_type": "XY4"},
"twirling": {
"enable_gates": True,
"num_randomizations": 1000,
"shots_per_randomization": 128,
},
"resilience": {
"measure_mitigation": True,
"measure_noise_learning": {
"num_randomizations": 32,
"shots_per_randomization": 100,
},
},
},
)
print("job ID (save this to reconnect later):", job.job_id)
job ID (save this to reconnect later): 43ed8d07-6d7d-4f33-b70a-7f31b765b310
إعادة الاتصال بمهمة طويلة الأمد

التشغيل واسع النطاق ليس سريعاً، ومعظم الوقت كلاسيكي وليس على QPU. يعمل ضغط AQC داخل الدالة قبل الوصول إلى QPU: عند 30 موقعاً مع max_bond=128 استغرق ذلك قرابة أربع ساعات في تشغيلنا، مقابل حوالي 18 دقيقة من وقت QPU المذكورة في تقدير الاستخدام في أعلى هذا الدليل التعليمي. وقت انتظار الطابور يُضاف فوق كليهما. لست بحاجة إلى إبقاء دفتر الملاحظات هذا أو النواة مفتوحة أثناء التشغيل.

انسخ معرّف المهمة الذي طبعته الخلية السابقة واحفظه. الخلايا الثلاث التالية تتيح لك استئناف العمل لاحقاً:

  1. إعادة الاتصال، لا حاجة إليها إلا في جلسة نواة جديدة: أعد تشغيل خلايا الإعداد لإعادة إنشاء serverless، ثم أعد بناء مقبض job من المعرّف الذي حفظته. تخطَّ هذه الخلية إذا كنت لا تزال في نفس الجلسة التي قدّمت فيها المهمة، لأن المقبض لا يزال نشطاً.

  2. تحقق من الحالة: أعد التشغيل حتى تُبلّغ عن DONE.

  3. استرجع النتيجة: شغّل فقط بعد أن تكون الحالة DONE.

خلية إعادة الاتصال التالية تحتوي على عنصر نائب. استبدله بـ job_id الخاص بك:

# Reconnect to a previously submitted job by its ID. Only needed in a NEW kernel
# session; if you are still in the session where you submitted, the `job` handle
# from the preceding cell is already live, so skip this cell. Replace the ID that follows with your own.
job = serverless.get_job_by_id("<your job ID>")
# Check where the job is. Re-run this until it reports DONE before fetching the
# result in the following cell: QUEUED -> INITIALIZING -> RUNNING: OPTIMIZING_FOR_HARDWARE ->
# RUNNING: WAITING_FOR_QPU -> RUNNING: EXECUTING_QPU -> RUNNING: POST_PROCESSING
# -> DONE.
print(job.status())
DONE
# Run this only once the preceding status cell reports DONE. result() blocks until
# the job finishes, so calling it earlier just waits (possibly for hours).
result = job.result()
print(
"AQC fidelities:",
{k: round(v, 4) for k, v in result["metadata"]["aqc_fidelities"].items()},
)

ev = np.array(result["expectation_values"])
Gjjc = ev[1:] # drop the t = 0 row -> shape (time_steps, n)
AQC fidelities: {'1': 1.0, '2': 0.9994, '3': 0.9944, '4': 0.9853, '5': 0.9747, '6': 0.959, '7': 0.9495, '8': 0.9542, '9': 0.9533, '10': 0.9451}

الخطوة 4: معالجة النتيجة بعد الحصول عليها وإعادتها بالشكل الكلاسيكي المطلوب

معالجة لاحقة مطابقة لتشغيل المحاكي: تحويل فورييه لدالة غرين إلى S(q,ω)S(q, \omega)، والتماثل المرآتي، وقص القيم السالبة. مع السلسلة والتطور الأطول، يصبح متصل الاثنين-سبينون محلولاً بشكل أفضل بكثير. يجب أن يملأ النطاق بين الحدود المتقطعة، بأعلى سطوع بالقرب من q=πq = \pi.

n = result["metadata"]["n"]
q_res, w_res = 100, 100
spectrum = get_spectrum(n, Gjjc, dt, time_steps, q_res, w_res)
spectrum = -(spectrum + spectrum[:, ::-1]) / 2 # mirror symmetry
spectrum = np.clip(spectrum, a_min=0, a_max=None) # clip negatives

plot_green(
n,
Gjjc,
time_steps,
dt,
title=f"Retarded Green's function - {n} qubits (AQC, hardware)",
)
plot_spectrum(
spectrum,
dt,
q_res,
w_res,
lower_bound=True,
upper_bound=True,
title=f"Dynamical structure factor - {n} qubits (AQC, hardware)",
)

Output of the previous code cell

Output of the previous code cell

الملحق

المثال العتادي السابق يشغّل طول سلسلة واحد فقط. الأطياف الثلاثة التالية تأتي من تشغيلات عتادية سابقة لنفس سير العمل هذا على ibm_pittsburgh عند 10 و20 و30 موقعاً، مع تثبيت كل مدخل آخر: 20 خطوة تروتر عند dt = 0.6، وخطة الضغط المكونة من ست خطوات مضغوطة بـ AQC بطبقة واحدة وأربع خطوات بطبقتين، وmax_bond = 128. هذه نتائج مسجّلة، وليست ناتجة عن الخلايا السابقة.

تُستخدم نفس الإعدادات في جميع الأحجام الثلاثة، لذا فإن الأطياف قابلة للمقارنة مباشرة. ضبط هذه الإعدادات حسب طول السلسلة، بمزيد من طبقات أنساتز الحالة الأرضية أو max_bond أكبر على سبيل المثال، يمكن أن يعطي نتائج أفضل من أي شيء موضح هنا.

Dynamical structure factor at 10 sites, a single sharp bright peak at q = pi near the lower bound

Dynamical structure factor at 20 sites, spectral weight filling the band between the two dashed two-spinon bounds

Dynamical structure factor at 30 sites, the continuum resolved more finely with fainter contrast and some weight outside the bounds

الخطوات التالية

توصيات