AQC + 트로터 동역학을 활용한 중성자 산란 시뮬레이션 서버리스 워크플로우
예상 소요 시간: Heron r3 프로세서 기준 18분 (참고: 이는 단지 예상치일 뿐입니다. (실행 시간은 다를 수 있습니다.)
학습 성과
- 비탄성 중성자 산란 스펙트럼이 1D 양자 자석의 동적 구조 인자 와 어떻게 대응하는가.
- 밀도 행렬 재정규화 군(DMRG) 및 행렬 곱 상태(MPS) 충실도 극대화를 이용하여 KCuF (등방성 하이젠베르크) 기저 상태를 준비하는 방법.
- Trotter 시간 진화, 근사 양자 컴파일링(AQC) 회로 압축 및 완화 실행을 단일 함수 호출로 실행하는 방법.
- 사이트별 시계열 데이터를 로 후처리하고, 두 스핀온 연속체를 식별하는 방법.
전제조건
- Qiskit 패턴,
SparsePauliOp, 및 에 대한 이해 트로터의 시간적 변화. - 텐서 네트워크 기법(DMRG 및 MPS)에 대한 기초적인 이해는 도움이 되지만 필수는 아니며, 이 함수가 트로터 회로를 압축하는 데 사용하는 라이브러리에
qiskit-addon-aqc-tensor대한 지식도 마찬가지입니다.
배경
비탄성 중성자 산란은 스핀-스핀 상관 함수의 시공간 푸리에 변환인 동적 구조 인자 를 측정하므로, 미시적 스핀 모델로부터 를 재현하는 것은 양자 시뮬레이션에 대한 직접적이고 반증 가능한 검증 방법이다. 이 튜토리얼에서는 KCuF를 다룹니다. 이는 스핀- 인 반강자성 하이젠베르크 사슬로, 여기 상태는 단일 스핀 반전이 아니라 분수화된 스피논 쌍으로 나타납니다. 즉, 는 날카로운 마그논 분산 곡선 대신, 아래쪽은 로, 위쪽은 로 경계 지워진 넓은 2-스피논 연속체를 보여줍니다. 이는 다음 그림에서 점선으로 표시된 곡선들입니다. 전체적인 물리학적 원리와 측정된 중성자 데이터와의 비교에 대해서는 원본 튜토리얼과 Lee 등(20XX)의 논문에서 다루고 있으며, arXiv:2603.15608.
양자 워크플로는 산란 실험을 반영합니다:
- 사슬의 기저 상태 를 준비합니다.
- 중심 지점에서 국소 섭동, 즉 -회전을 가하여, 중성자로부터의 운동량 및 에너지 전달을 모방해 봅시다.
- 트로터 곱 공식(Trotter product formula)을 사용하여 하이젠베르크 해밀토니안 하에서 시간 발전을 수행한다.
- 사이트별 자화 를 측정합니다. 이는 사이트 와 시간 에 대한 함수로서, 정확히 지연 그린 함수 와 동일하므로, 5단계에서 푸리에 변환을 수행하기 전에 별도의 변환 과정이 필요하지 않습니다.
- 푸리에 변환을 통해 를 로 변환한다.
3단계에서, 긴 진화 과정에 대한 정확한 트로터 회로가 하드웨어가 처리하기에는 너무 깊어질 때 문제가 발생할 수 있다. 텐서 네트워크를 활용한 AQC는 트로터 단계의 한 블록을 고정된 얕은 매개변수화 안자츠로 압축함으로써 이 문제를 해결하며, 이 안자츠의 정확한 진화에 대한 상태 충실도는 MPS 시뮬레이터를 통해 고전적으로 최대화됩니다( arXiv:2301.08609 ). AQC Dynamics 템플릿 은 이 전체 양자 코어(트로터 합성, AQC 압축, 완화 실행)를 하나의 호출로 묶어 제공합니다:
PRE (이 노트) | FUNCTION ( aqc-dynamics-function) | 게시하기 (이 노트) |
|---|---|---|
| DMRG와 MPS 정확도 극대화를 통해 도출한 기저 상태. 중성자 킥이 동일한 회로에 반영되어 있음 | 트로터 합성 → AQC 압축 → statevector, fake, 또는 에서 실행하여 runtime 각 사이트당 를 반환 | , 동적 구조 인자 |
실험에 특화된 작업은 이 노트북에 남아 있습니다: 기저 상태 준비(PRE)와 ‘ ’ 후처리(POST). 양자 연산이 많이 필요한 두 단계, 즉 압축과 실행은 함수 내부에서 수행됩니다.
이 튜토리얼은 “양자 회로를 이용한 양자 물질에서의 중성자 산란 시뮬레이션” 문서의 보조 자료로, 해당 문서와 동일한 실험을 단계별로 구현합니다. 즉, 동일한 KCuF 모델, 기저 상태 준비, 중성자 킥, 후처리를 다루며, 트로터 합성, AQC 압축, 완화 실행 과정을 단계별로 상세히 설명합니다. AQC 압축이 어떻게 작동하는지 알아보려면 해당 튜토리얼을 읽어보세요. 배포된 함수 템플릿을 통해 동일한 실험을 실행하려면 이 글을 읽어보세요. 양자 코어가 단일 함수 호출로 변환되며, 몇 시간씩 걸리는 AQC 압축 작업이 사용자의 컴퓨터가 아닌 서버리스 워커 내에서 실행되므로, 작업이 진행되는 동안 HPC 시스템이나 오픈 커널이 필요하지 않습니다. 이 호출은 다른 ‘ 1D ’ 역학 실험들도 구동합니다.
요구사항
이 튜토리얼을 시작하기 전에 다음 사항이 준비되어 있는지 확인하십시오:
-
Qiskit Serverless 계정에 배포된 함수입니다. 먼저 컴패니언 함수 템플릿을 실행하십시오: AQC + Trotter 동역학 함수 템플릿을 배포하고 실행하십시오. 이 가이드에서는 소스 파일을 가져오고 해당 함수를 귀하의 계정에 업로드하는 방법을 단계별로 안내합니다. 이 튜토리얼에서는 배포된 함수만 호출합니다.
-
IBM Quantum® (
QiskitServerless함수 템플릿 참조)에 저장된 자격 증명. 이 튜토리얼에 나오는 두 예제 모두 배포된 함수를 호출하므로, 두 예제 모두 해당 함수가 필요합니다. -
Qiskit SDK v2.0 또는 그 이후 버전 (
pip install qiskit). -
Qiskit IBM 카탈로그 클라이언트 (
pip install qiskit-ibm-catalog). -
NumPy, SciPy, 그리고 Matplotlib (
pip install numpy scipy matplotlib). 기저 상태 준비에 사용되는 COBYQA 최적화기를 사용하려면 SciPy 1.14 이상의 버전이 필요합니다. -
AQC 텐서 네트워크 스택은, 1단계의 기저 상태 준비 과정이 이 노트북 내에서 로컬로 실행되기 때문입니다:
pip install 'qiskit-addon-aqc-tensor[quimb-jax]==0.3.1'.
새로 배포된 함수에 대한 첫 번째 호출은 서버리스 워커가 의존성을 설치하는 동안 대기하므로, 해당 실행 시 추가적인 지연 시간이 발생할 수 있습니다.
설정
라이브러리를 불러오고, 나중에 사용될 실험별 헬퍼 함수들을 정의합니다: ( build_gs_ansatz 기저 상태 준비를 위한 해밀토니안 변분 접근법, 즉 HVA), prepare_ground_state (DMRG 및 MPS 충실도 극대화), 그리고 get_spectrum, plot_green, 및 plot_spectrum ( 후처리). 이 내용은 원본 중성자 산란 튜토리얼을 바탕으로 수정 및 보완한 것입니다.
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.")Output:
Setup complete - helpers defined.
함수 템플릿 불러오기
Qiskit Serverless 에 접속하여 배포된 파일을 불러오세요 aqc-dynamics-function. 이 튜토리얼에 나오는 두 예제 모두 동일한 핸들을 fn 호출하므로, 이 함수는 여기서 한 번만 로드됩니다.
# Credentials are read from the account saved once via QiskitServerless.save_account(...)
serverless = QiskitServerless()
fn = serverless.load("aqc-dynamics-function")소규모 시뮬레이터 예시
먼저 정확한 statevector 백엔드를 사용하여 10개 사이트로 구성된 소규모 체인에서 전체 워크플로를 실행합니다. 이를 통해 QPU 실행 시간을 사용하기 전에 PRE → FUNCTION → POST 파이프라인을 검증합니다.
1단계: 고전적 입력을 양자 문제에 매핑하기
KCuF 해밀토니안을 ( SparsePauliOp 등방성 하이젠베르크: ) 형태로 구성하되, 각 가장 가까운 이웃 결합에 대해 결합 상수 를 적용한다. 이 끈들은 파울리 연산자이므로, 를 통해 스핀- 결합을 얻을 수 있다. DMRG와 MPS-정확도 극대화를 통해 기저 상태를 준비한 다음, 중성자 킥을 적용합니다: 중심 사이트에서 회전을 가합니다. 이렇게 준비된 회로가 바로 함수에 로 전달되는 것입니다 initial_state. 우리는 이를 기본값(사이트별 )으로 observables 그대로 두는데, 이는 중성자 워크플로우에 필요한 출력값과 정확히 일치합니다.
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}."
)Output:
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단계)입니다. 이 함수 템플릿은 두 가지를 하나의 호출로 통합합니다. 이 과정에서는 트로터 합성, AQC 압축 및 하드웨어 트랜스파일링을 수행한 뒤, 회로를 실행합니다(이 단계에서는 정확한 시뮬레이터에서 실행되며, 이후 하드웨어에서는 내장된 오류 완화 기능을 적용하여 실행됩니다). 두 가지 튜닝 매개변수는 aqc_segments (압축 계획)과 aqc_options (MPS 및 최적화기 설정)입니다. 각 세그먼트는 연속된 k 트로터 단계를 m-단계 트로터 목표값으로 구성된 안자츠로 {"n_steps": k, "ansatz_steps": m} 압축하며, 이를 초과하는 단계는 일반 트로터 방식으로 sum(n_steps) 실행됩니다. 초기 단계의 낮은 얽힘을 가진 단계들은 얕은 (ansatz_steps=1) 안자츠로 잘 압축되므로, 여기서는 처음 세 단계를 단일층 안자츠로, 다음 두 단계를 더 깊은 2층 안자츠로 압축합니다. 10개의 트로터 단계 중 나머지 5개는 일반 트로터 방식으로 실행됩니다. 이는 원본 튜토리얼을 그대로 따르는 것으로, MPS 결합 치수 max_bond=32, cutoff=1e-8, 그리고 반복 횟수를 100회로 제한한 L-BFGS-B 최적화기를 사용합니다 aqc_options .
Setup에서 로드된 함수를 호출하십시오. backend="statevector" 정확한 참조 경로를 실행합니다: QPU 시간이 소요되지 않으며, 회로는 서버리스 워커 내부의 정확한 상태 벡터 시뮬레이터에서 실행됩니다(이를 호출하려면 여전히 저장된 Qiskit Serverless 계정이 필요합니다). 는 준비된 기저 상태(킥 포함)를 initial_state 나타내며, observables 는 생략되어 이 함수가 기본 사이트당 를 측정하도록 합니다.
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 DONEOutput:
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)Output:
AQC fidelities: {'1': 1.0, '2': 0.9999, '3': 0.9992, '4': 0.9998, '5': 0.9995}
Green's function shape: (10, 10)
4단계: 후처리를 수행하고 원하는 기존 형식으로 결과를 반환합니다
그린 함수에 푸리에 변환을 적용해 를 구하고, 대칭 변환을 수행한 뒤 음수 값을 잘라내는 것이 표준 중성자 후처리 과정입니다. 이 모델의 경우 이므로 미러링은 정확히 이루어지며, 남아 있는 음수 값들은 유한하고 이산적으로 샘플링된 시계열을 푸리에 변환할 때 발생하는 인공적인 현상이므로 0으로 잘립니다. 이 작은 정확한 시뮬레이션에서는 2스핀온 연속체가 대략적으로만 분해되지만, 그 작동 원리는 뒤이어 수행되는 하드웨어 시뮬레이션과 동일합니다.
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:
대규모 하드웨어 예시
과학 코드의 변경 없이 동일한 워크플로우를 확장할 수 있습니다. 30개 사이트 체인, 두 배의 트로터 깊이(20단계), 다양한 가설 깊이를 사용하는 압축 계획(후반부의 더 얽힌 단계에는 더 깊은 가설 사용), 그리고 함수에 내장된 오류 완화 기능(동적 분리, 파울리 트월링, 트월링된 판독 오류 소멸(TREX))을 갖춘 IBM Quantum 프로세서에서의 실행이 포함됩니다. 시뮬레이터 예제와 동일한 4단계 과정을 따르며, Setup에서 생성된 fn 핸들을 재사용합니다.
작은 축척 | 큰 축척 | |
|---|---|---|
| 큐비트 | 1,000만 | 6시간에서 30분 |
| 트로터 스텝 | 1,000만 | 20 |
| AQC 압축 단계 (1층 + 2층) | 3 + 2 = 5 | 6 + 4 = 10 |
| 기저 상태 가설 층 | 3 | 5 |
| MPS 최대 본드 치수 | 32 | 128 |
| 백엔드 | statevector | DD, 파울리 회전, TREX가 적용된 QPU |
1단계: 고전적 입력을 양자 문제에 매핑하기
KCuF와 동일한 하이젠베르크 구조를 구축하고 SparsePauliOp , 더 긴 사슬에 대해 더 깊은 안자츠 gs_layers=5 (ansatz)를 적용하여 기저 상태를 준비한 다음, 중앙 사이트에 중성자 킥을 주입합니다. 이는 소규모 매핑과 동일하지만, 에서 확인할 수 있습니다.
10-site 시뮬레이션보다 낮은 기저 상태 충실도를 예상할 수 있습니다. 5개의 HVA 층으로는 30-site 기저 상태를 완전히 포착할 수 없기 때문에, 여기서는 약 0.82 의 값이 나오는 반면, 더 작은 사슬의 경우 0.98 의 값을 보였습니다. 이는 실패라기보다는 예상되는 현상이며, 같은 이유로 원본 튜토리얼에서는 50개 사이트에서 대략 0.65 을 허용하고 있습니다. COBYQA 반복 횟수 상한을 높이면 성능이 향상되지만 gs_layers , 추가적인 고전적 비용이 발생합니다.
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}."
)Output:
DMRG ground-state energy: -13.111355
GS fidelity: 0.8201
Prepared 30-qubit ground state with the neutron kick at site 14.
2단계 및 3단계: 함수 템플릿을 사용하여 압축하고 실행하기
시뮬레이터 예제와 동일한 단일 호출이지만, 이번에는 IBM Quantum 프로세서를 backend_name 가리키도록 설정되어 있어, 해당 함수가 그곳에서 트랜스파일링되어 실행됩니다. 압축 계획에 따라 안자츠의 깊이가 달라집니다. 처음 6단계(저얽힘)의 트로터 단계는 얕은 단일층 안자츠로 압축되고, 다음 4단계는 더 깊은 2층 안자츠로 압축되며, 20단계 중 나머지 10단계는 일반 트로터 방식으로 실행됩니다. aqc_options 더 길고 얽힘이 더 심한 사슬에 대해 max_bond=128 MPS 결합 차원을 로 높이며(원본과 일치), L-BFGS-B 최적화기의 반복 횟수 상한을 100회로 유지합니다. 내장된 오류 완화 기능, 즉 동적 분리( XY4 ), 게이트 트위링 및 TREX 측정 완화 기능을 활성화합니다 estimator_options . 이 함수의 기본값은 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)Output:
job ID (save this to reconnect later): 43ed8d07-6d7d-4f33-b70a-7f31b765b310
대규모 실행은 속도가 빠르지 않으며, 대부분의 시간은 QPU가 아닌 클래식 환경에서 소요됩니다. AQC 압축은 어떤 데이터도 QPU에 도달하기 전에 함수 내부에서 수행됩니다. 30개 사이트에서 이 max_bond=128 작업을 수행했을 때, 저희 테스트에서는 거의 4시간이 소요되었는데, 이는 이 튜토리얼 상단의 ‘사용량 추정’에 명시된 QPU 처리 시간인 약 18분과는 대조적입니다. 대기 시간은 이 두 가지에 더해집니다. 이 노트북이나 커널이 실행되는 동안 이를 계속 열어둘 필요는 없습니다.
앞의 셀에 표시된 작업 ID를 복사하여 저장하십시오. 다음 세 개의 셀을 통해 나중에 작업을 다시 이어갈 수 있습니다:
- 다시 연결: 새로운 커널 세션에서만 필요합니다. ‘Setup cells’를 다시 실행하여 재구성한 다음
serverless, 저장해 둔 ID를 사용하여job핸들을 다시 생성하십시오. 제출한 세션에 아직 머물러 있다면 이 셀을 건너뛰세요. 핸들이 이미 활성화된 상태이기 때문입니다. - 상태 확인: 결과가 표시될 때까지 다시 실행하십시오
DONE. - 결과 가져오기: 상태가 일 때만 실행합니다
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())Output:
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)Output:
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단계: 후처리를 수행하고 원하는 기존 형식으로 결과를 반환합니다
시뮬레이터 실행과 동일한 후처리 과정을 거칩니다: 그린 함수에 푸리에 변환을 적용하여 를 구하고, 대칭 변환을 수행한 뒤 음수 값을 잘라냅니다. 더 긴 사슬과 진화를 통해, 두 스핀온 연속체가 훨씬 더 명확하게 구분됩니다. 점선으로 표시된 경계 사이의 영역을 채워야 하며, 근처에서 가장 밝아야 합니다.
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:
부록
앞서 소개한 하드웨어 예제는 단일 체인 길이를 실행합니다. 다음에 제시된 세 가지 스펙트럼은 동일한 워크플로우를 10, 20, 30개 ibm_pittsburgh 사이트에서 수행한 초기 하드웨어 실행 결과로, 그 외 모든 입력 조건은 고정된 상태입니다. 즉, 에서 20개의 트로터 단계 dt = 0.6, 6개의 1층 단계와 4개의 2층 AQC 압축 단계를 포함한 압축 계획, 그리고 입니다 max_bond = 128. 이것들은 기록된 결과이며, 앞선 셀에서 산출된 결과가 아닙니다.
세 가지 크기 모두에서 동일한 설정이 사용되었으므로, 스펙트럼을 직접 비교할 수 있습니다. 예를 들어, 체인 길이에 따라 조정하거나, 기저 상태 가설 층을 더 추가하거나 max_bond, 를 더 크게 설정하는 등의 방법을 사용하면, 여기에서 제시된 어떤 결과보다 더 나은 결과를 얻을 수 있습니다.



다음 단계
- 이 워크플로를 자신의 시스템에 맞게 조정하십시오. 이 함수는 어떤 1D 의 최인접 이웃(nearest-neighbor) 방식이든 수용하므로
SparsePauliOp, 다른 체인 해밀토니안, 초기 상태 또는 관측량 집합을 사용하더라도 PRE → FUNCTION → POST 파이프라인은 동일하게 실행됩니다. GitHub 의 AQC Dynamics 템플릿에서 전체 입출력 계약서를 확인하세요. - 이 벤치마크가 수록된 논문을 읽어보세요: Lee 외, 중성자 산란 실험을 통한 양자 시뮬레이션 벤치마킹 ( arXiv:2603.15608 ).
- 원본 “중성자 산란 시뮬레이션” 튜토리얼 과 비교해 보면, 이 튜토리얼의 인라인 워크플로는 배포된 함수 템플릿으로 이식된 것입니다.
- 하드웨어 실행에 적용된 오류 완화 및 억제 기법, 즉 동적 분리(dynamical decoupling), 파울리 트위링(Pauli twirling), TREX에 대해 더 자세히 알아보세요.