Sari la conținutul principal

Simulează împrăștierea de neutroni cu un flux de lucru Serverless AQC + dinamică Trotter

Estimare de utilizare: 18 minute pe un procesor Heron r3 (NOTĂ: Aceasta este doar o estimare. Timpul tău de rulare poate varia.)

Rezultate de învățare

  • Cum se mapează un spectru de împrăștiere inelastică de neutroni la factorul de structură dinamică S(q,ω)S(q, \omega) al unui magnet cuantic 1D.

  • Cum să pregătești starea fundamentală KCuF3_3 (Heisenberg izotropic) cu grupul de renormalizare a matricei densității (DMRG) și maximizarea fidelității stării produs matricial (MPS).

  • Cum să rulezi evoluția temporală Trotter, compresia circuitului prin compilare cuantică aproximativă (AQC) și execuția atenuată ca un singur apel de funcție.

  • Cum să post-procesezi seria temporală σz(t)\langle \sigma_z \rangle(t) per site în S(q,ω)S(q, \omega) și să identifici continuum-ul cu doi spinoni.

Cerințe preliminare

Context

Împrăștierea inelastică de neutroni măsoară factorul de structură dinamică S(q,ω)S(q, \omega), transformata Fourier spațio-temporală a funcției de corelație spin-spin, astfel încât reproducerea S(q,ω)S(q, \omega) dintr-un model de spin microscopic este un test direct, falsificabil al unei simulări cuantice. Acest tutorial studiază KCuF3_3, un lanț Heisenberg antiferomagnetic de spin-12\frac{1}{2} ale cărui excitații nu sunt inversiuni de spin simple, ci perechi de spinoni fracționați: în loc de o dispersie magnonică ascuțită, S(q,ω)S(q, \omega) arată un continuum larg cu doi spinoni, mărginit inferior de π2sinq\tfrac{\pi}{2}|\sin q| și superior de πsin(q/2)\pi|\sin(q/2)|. Acestea sunt curbele punctate din graficele care urmează. Fizica în întregime, și comparația cu datele măsurate de neutroni, sunt acoperite în tutorialul original și în Lee et al., arXiv:2603.15608.

Fluxul de lucru cuantic reflectă experimentul de împrăștiere:

  1. Pregătește starea fundamentală a lanțului ψ0|\psi_0\rangle.

  2. Aplică o perturbație locală în site-ul central, o rotație ZZ de π/2\pi/2, imitând transferul de impuls și energie de la neutron.

  3. Evoluează temporal sub Hamiltonianul Heisenberg, eiHte^{-iHt}, cu o formulă de produs Trotter.

  4. Măsoară magnetizarea per site σzj(t)\langle \sigma_z^j \rangle(t). Ca funcție de site-ul jj și timpul tt, aceasta este exact funcția Green retardată GR(j,jc,t)G^R(j, j_c, t), deci nu este necesară nicio conversie înainte de transformata Fourier din pasul 5.

  5. Transformă Fourier GRG^R în S(q,ω)S(q, \omega).

Pot apărea probleme la pasul 3, când circuitele Trotter exacte pentru evoluții lungi devin prea adânci pentru hardware. AQC cu rețele tensoriale rezolvă aceasta comprimând un bloc de pași Trotter într-un ansatz parametrizat fix, puțin adânc, a cărui fidelitate a stării față de evoluția exactă este maximizată clasic cu un simulator MPS (arXiv:2301.08609). Șablonul AQC Dynamics împachetează întregul nucleu cuantic (sinteză Trotter, compresie AQC și execuție atenuată) în spatele unui singur apel:

PRE (acest notebook)FUNCȚIE (aqc-dynamics-function)POST (acest notebook)
Stare fundamentală din DMRG plus maximizarea fidelității MPS, cu impulsul de neutron integrat în același circuitSinteză Trotter → compresie AQC → execuție pe statevector, fake, sau runtime, returnând σzj(t)\langle \sigma_z^j \rangle(t) per siteS(q,ω)S(q, \omega), factorul de structură dinamică

Lucrul specific experimentului rămâne aici în notebook: pregătirea stării fundamentale (PRE) și post-procesarea S(q,ω)S(q, \omega) (POST). Cei doi pași intensivi cuantic, compresia și execuția, rulează în interiorul funcției.

Acest tutorial este un companion al Simulează împrăștierea de neutroni în materiale cuantice cu circuite cuantice, care construiește același experiment inline: același model KCuF3_3, pregătirea stării fundamentale, impulsul de neutron și post-procesarea, cu sinteza Trotter, compresia AQC și execuția atenuată scrise pas cu pas. Citește acel tutorial pentru a afla cum funcționează compresia AQC. Citește-l pe acesta pentru a rula același experiment printr-un șablon de funcție implementat: nucleul cuantic devine un singur apel de funcție, iar compresia AQC de câteva ore rulează în interiorul worker-ului Serverless în loc de pe mașina ta, deci nu ai nevoie de un sistem HPC sau de un kernel deschis în timp ce rulează. Același apel conduce și alte experimente de dinamică 1D.

Cerințe

Înainte de a începe acest tutorial, asigură-te că ai următoarele:

  • Funcția implementată în contul tău Qiskit Serverless. Rulează mai întâi șablonul de funcție companion: Implementează și rulează șablonul de funcție de dinamică AQC + Trotter. Acel ghid te conduce prin obținerea fișierelor sursă și încărcarea funcției în contul tău. Acest tutorial doar apelează funcția implementată.

  • Acreditări IBM Quantum® salvate pentru QiskitServerless (vezi șablonul de funcție). Ambele exemple din acest tutorial apelează funcția implementată, deci ambele au nevoie de ele.

  • Qiskit SDK v2.0 sau mai recent (pip install qiskit).

  • Clientul Qiskit IBM Catalog (pip install qiskit-ibm-catalog).

  • NumPy, SciPy și Matplotlib (pip install numpy scipy matplotlib). SciPy 1.14 sau mai recent este necesar pentru optimizatorul COBYQA folosit în pregătirea stării fundamentale.

  • Stiva de rețele tensoriale AQC, deoarece pregătirea stării fundamentale din Pasul 1 rulează local în acest notebook: pip install 'qiskit-addon-aqc-tensor[quimb-jax]==0.3.1'.

Primul apel către o funcție nou implementată așteaptă în timp ce worker-ul Serverless instalează dependențele sale, deci așteaptă latență suplimentară la acea rulare.

Configurare

Importă bibliotecile și definește funcțiile ajutătoare specifice experimentului folosite mai târziu: build_gs_ansatz (ansatz-ul variațional Hamiltonian, sau HVA, pentru pregătirea stării fundamentale), prepare_ground_state (DMRG plus maximizarea fidelității MPS) și get_spectrum, plot_green și plot_spectrum (post-procesarea S(q,ω)S(q, \omega)). Acestea sunt adaptate din tutorialul original de împrăștiere de neutroni.

# 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.

Încarcă șablonul de funcție

Conectează-te la Qiskit Serverless și încarcă funcția implementată aqc-dynamics-function. Ambele exemple din acest tutorial apelează același handle fn, deci funcția este încărcată o singură dată, aici.

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

Exemplu de simulator la scară mică

Rulăm mai întâi fluxul de lucru complet pe un lanț mic de 10 site-uri folosind backend-ul exact statevector. Aceasta validează pipeline-ul PRE → FUNCȚIE → POST înainte de a cheltui vreun timp QPU.

Pasul 1: Mapează intrările clasice la o problemă cuantică

Construiește Hamiltonianul KCuF3_3 ca un SparsePauliOp (Heisenberg izotropic: XX+YY+ZZXX + YY + ZZ la cuplajul 14\tfrac14 pe fiecare legătură între vecini apropiați; șirurile sunt operatori Pauli, deci 14\tfrac14 dă cuplajul de spin-12\frac{1}{2}). Pregătește starea fundamentală cu DMRG plus maximizarea fidelității MPS, apoi integrează impulsul de neutron: o rotație ZZ de π/2\pi/2 la site-ul central. Circuitul pregătit este ceea ce predăm funcției ca initial_state. Lăsăm observables la valoarea implicită (Z per site), care este exact citirea σzj(t)\langle \sigma_z^j \rangle(t) de care are nevoie fluxul de lucru de neutroni.

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.

Pașii 2 și 3: Comprimă și execută cu șablonul de funcție

Într-un flux de lucru scris manual, acestea sunt două etape separate: optimizarea circuitelor pentru hardware (Pasul 2) și execuția lor (Pasul 3). Șablonul de funcție le colapsează pe amândouă într-un singur apel. Efectuează sinteza Trotter, compresia AQC și transpilarea pentru hardware, apoi rulează circuitele (aici pe simulatorul exact, mai târziu cu atenuare de eroare încorporată pe hardware). Cei doi parametri de ajustare sunt aqc_segments (planul de compresie) și aqc_options (setările MPS și ale optimizatorului). Fiecare segment {"n_steps": k, "ansatz_steps": m} comprimă k pași Trotter consecutivi într-un ansatz construit dintr-o țintă Trotter de m pași, iar orice pași dincolo de sum(n_steps) rulează ca Trotter simplu. Pașii timpurii, cu entanglement redus, se comprimă bine într-un ansatz puțin adânc (ansatz_steps=1), deci aici comprimăm primii trei pași într-un ansatz cu un singur strat și următorii doi într-un ansatz mai adânc cu două straturi; restul de cinci din cei 10 pași Trotter rulează ca Trotter simplu. Pentru aqc_options reflectăm tutorialul original: dimensiunea legăturii MPS max_bond=32, cutoff=1e-8 și un optimizator L-BFGS-B plafonat la 100 de iterații.

Apelează funcția încărcată în Setup. backend="statevector" rulează calea de referință exactă: fără timp QPU, circuitele rulând pe un simulator statevector exact în interiorul worker-ului serverless (un cont Qiskit Serverless salvat este în continuare necesar pentru a o apela). initial_state transportă starea fundamentală pregătită (inclusiv kick-ul); observables este omis, astfel încât funcția măsoară ZZ implicit pentru fiecare sit.

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)

Pasul 4: Post-procesează și returnează rezultatul în formatul clasic dorit

Transformă Fourier funcția Green în S(q,ω)S(q, \omega), simetrizează în oglindă și taie valorile negative: post-procesarea standard de neutroni. Simetrizarea în oglindă este exactă deoarece S(q,ω)=S(q,ω)S(q, \omega) = S(-q, \omega) pentru acest model, iar valorile negative care supraviețuiesc sunt artefacte ale transformării Fourier a unei serii temporale finite, eșantionate discret, deci sunt tăiate la zero. În această rulare mică exactă, continuum-ul cu doi spinoni este rezolvat doar aproximativ, dar mecanismul este identic cu rularea pe hardware care urmează.

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

Exemplu de hardware la scară mare

Același flux de lucru se scalează fără a schimba niciun cod științific: un lanț de 30 de site-uri, dublul adâncimii Trotter (20 de pași), un plan de compresie care variază adâncimea ansatz-ului (un ansatz mai adânc pentru pașii ulteriori, mai entanglați) și execuția pe un procesor IBM Quantum cu atenuarea de eroare încorporată a funcției (decuplare dinamică, twirling Pauli și eliminarea erorii de citire prin twirling (TREX)). Parcurgem aceiași patru pași ca exemplul de simulator, reutilizând handle-ul fn din Configurare.

Scară micăScară mare
Qubiți1030
Pași Trotter1020
Pași comprimați AQC (1 strat + 2 straturi)3 + 2 = 56 + 4 = 10
Straturi ansatz stare fundamentală35
Dimensiune maximă legătură MPS32128
BackendstatevectorQPU cu DD, twirling Pauli și TREX

Pasul 1: Mapează intrările clasice la o problemă cuantică

Construiește același SparsePauliOp Heisenberg KCuF3_3 și pregătește starea fundamentală, acum cu un ansatz mai adânc gs_layers=5 pentru lanțul mai lung, apoi integrează impulsul de neutron ZZ de π/2\pi/2 la site-ul central. Aceasta este identică cu maparea la scară mică, dar la n=30n = 30.

Așteaptă-te la o fidelitate mai mică a stării fundamentale decât rularea de 10 site-uri: în jur de 0,82 aici față de 0,98 pentru lanțul mai mic, deoarece cinci straturi HVA nu pot captura complet o stare fundamentală de 30 de site-uri. Acest lucru este așteptat, nu un eșec, iar tutorialul original acceptă aproximativ 0,65 la 50 de site-uri din același motiv. Creșterea gs_layers sau a plafonului de iterații COBYQA o îmbunătățește, la cost clasic suplimentar.

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.

Pașii 2 și 3: Comprimă și execută cu șablonul de funcție

Același apel unic ca în exemplul de simulator, acum cu backend_name indicând un procesor IBM Quantum, astfel încât funcția transpilează și execută acolo. Planul de compresie variază adâncimea ansatz-ului: primii șase pași Trotter (cu entanglement redus) se comprimă într-un ansatz puțin adânc cu un singur strat, următorii patru într-un ansatz mai adânc cu două straturi, iar restul de 10 din cei 20 de pași rulează ca Trotter simplu. aqc_options ridică dimensiunea legăturii MPS la max_bond=128 pentru lanțul mai lung, mai entanglat (potrivindu-se cu originalul), păstrând același optimizator L-BFGS-B plafonat la 100 de iterații. estimator_options activează atenuarea de eroare încorporată: decuplare dinamică (XY4), twirling de porți și atenuarea măsurării TREX. Valorile implicite ale funcției se potrivesc deja cu tutorialul original pentru toate acestea, cu excepția bugetului de învățare TREX (measure_noise_learning). Întregul bloc este totuși scris în întregime deoarece un estimator_options furnizat de apelant înlocuiește în întregime valorile implicite ale funcției, în loc să se îmbine cu ele, deci omiterea unei chei ar reveni la valoarea implicită IBM Quantum Compute în loc de cea a funcției.

# 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
Reconectarea la un job de lungă durată

Rularea la scară mare nu este rapidă, iar cea mai mare parte a timpului este clasică, nu pe QPU. Compresia AQC rulează în interiorul funcției înainte ca orice să ajungă la QPU: la 30 de situri cu max_bond=128, acest lucru a durat aproape patru ore în rularea noastră, față de aproximativ 18 minute de timp de QPU menționate în Estimarea de utilizare de la începutul acestui tutorial. Timpul de așteptare în coadă se adaugă la ambele. Nu este nevoie să păstrezi acest notebook sau kernel deschis în timp ce rulează.

Copiază ID-ul job-ului afișat de celula precedentă și salvează-l. Următoarele trei celule îți permit să reiei rularea mai târziu:

  1. Reconectare, necesară doar într-o sesiune nouă de kernel: rulează din nou celulele Setup pentru a recrea serverless, apoi reconstruiește handle-ul job din ID-ul salvat. Sari peste această celulă dacă ești încă în sesiunea în care ai trimis job-ul, deoarece handle-ul este deja activ.

  2. Verifică starea: rulează din nou până când raportează DONE.

  3. Preia rezultatul: rulează doar după ce starea este DONE.

Următoarea celulă de reconectare conține un substituent (placeholder). Înlocuiește-l cu propriul tău 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}

Pasul 4: Post-procesarea și returnarea rezultatului în formatul clasic dorit

Post-procesare identică cu rularea din simulator: transformă Fourier funcția Green în S(q,ω)S(q, \omega), simetrizează prin oglindire și taie valorile negative. Cu lanțul și evoluția mai lungi, continuumul de doi spinoni este mult mai bine rezolvat. Ar trebui să umple banda dintre limitele punctate, cel mai luminos aproape de 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

Apendice

Exemplul hardware precedent rulează o singură lungime de lanț. Cele trei spectre care urmează provin din rulări hardware anterioare ale acestui aceluiași flux de lucru pe ibm_pittsburgh la 10, 20 și 30 de situri, cu toate celelalte input-uri păstrate fixe: 20 de pași Trotter la dt = 0.6, planul de compresie de șase pași cu un strat plus patru pași cu două straturi comprimați AQC, și max_bond = 128. Acestea sunt rezultate înregistrate, nu ieșiri din celulele precedente.

Aceleași setări sunt folosite la toate cele trei dimensiuni, astfel încât spectrele sunt direct comparabile. Ajustarea lor în funcție de lungimea lanțului, de exemplu cu mai multe straturi de ansatz pentru starea fundamentală sau un max_bond mai mare, poate oferi rezultate mai bune decât cele prezentate aici.

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

Pași următori

Recomandări