Simular a dispersão de nêutrons com um fluxo de trabalho sem servidor utilizando AQC + dinâmica de Trotter
Estimativa de tempo de execução: 18 minutos em um processador Heron r3 (NOTA: Trata-se apenas de uma estimativa. (O tempo de execução pode variar.)
Use este tutorial para realizar o experimento de espalhamento de nêutrons por meio de uma função implementada no Qiskit Serverless, que agrupa a construção, a compactação e a execução do circuito em uma única chamada. A compactação utiliza os recursos de computação e memória do worker sem servidor e pode continuar mesmo depois que você fechar seu notebook; a preparação do estado fundamental e o pós-processamento ainda são executados localmente. Primeiro, você deve implantar o modelo de função. Para aprender a implementação passo a passo, consulte o tutorial original.
Resultados do aprendizado
- Como um espectro de espalhamento inelástico de nêutrons se relaciona com o fator de estrutura dinâmico de um ímã quântico do tipo “ 1D ”.
- Como preparar o estado fundamental KCuF (Heisenberg isotrópico) utilizando o grupo de renormalização da matriz de densidade (DMRG) e a maximização da fidelidade do estado de produto matricial (MPS).
- Como executar a evolução temporal do Trotter, a compactação de circuitos por compilação quântica aproximada (AQC) e a execução mitigada por meio de uma única chamada de função.
- Como realizar o pós-processamento das séries temporais de “ ” por local, transformando-as em “ ” e identificando o continuum de dois spinons.
Pré-requisitos
- Conhecimento dos padrões do Qiskit e
SparsePauliOpda evolução temporal de Trotter. - É útil ter um conhecimento básico dos métodos de redes tensoriais (DMRG e MPS), mas não é obrigatório, assim como a familiaridade com a
qiskit-addon-aqc-tensorbiblioteca que a função utiliza para compactar circuitos de Trotter.
Segundo plano
A dispersão inelástica de nêutrons mede o fator de estrutura dinâmico , que é a transformada de Fourier no espaço e no tempo da função de correlação spin-spin; portanto, a reprodução de a partir de um modelo microscópico de spin constitui um teste direto e falsificável de uma simulação quântica. Este tutorial estuda KCuF, uma cadeia de Heisenberg antiferromagnética spin- , cujas excitações não são inversões de spin únicas, mas pares de spinons fracionados: em vez de uma dispersão nítida de magnons, mostra um amplo continuum de dois spinons, limitado inferiormente por e superiormente por . Essas são as curvas tracejadas nos gráficos a seguir. A física completa e a comparação com os dados medidos de nêutrons são abordadas no tutorial original e em Lee et al., arXiv:2603.15608.
O fluxo de trabalho quântico reflete o experimento de espalhamento:
- Prepare o estado fundamental da cadeia .
- Comece com uma perturbação local no ponto central, uma rotação do tipo “ ” , imitando a transferência de momento e energia do nêutron.
- Evoluir no tempo sob o hamiltoniano de Heisenberg, , utilizando a fórmula do produto de Trotter.
- Meça a magnetização por site . Em função do site e do tempo , essa é exatamente a função de Green retardada ; portanto, não é necessária nenhuma conversão antes da transformada de Fourier na etapa 5.
- A transformada de Fourier de resulta em .
Podem surgir problemas na etapa 3, quando os circuitos de Trotter exatos para evoluções longas se tornam complexos demais para o hardware. O AQC com redes tensoriais resolve essa questão ao compactar um bloco de passos de Trotter em um ansatz parametrizado fixo e raso, cuja fidelidade do estado em relação à evolução exata é maximizada classicamente com um simulador MPS ( arXiv:2301.08609 ). O modelo AQC Dynamics agrupa todo esse núcleo quântico (síntese de Trotter, compactação AQC e execução mitigada) em uma única chamada:
PRE (este caderno) | FUNÇÃO ( aqc-dynamics-function) | PUBLICAR (este caderno) |
|---|---|---|
| Estado fundamental obtido por DMRG combinado com a maximização da fidelidade do MPS, com o “kick” do nêutron incorporado ao mesmo circuito | Síntese de Trotter → compressão AQC → execução em statevector, fake, ou runtime, retornando um e por local | o, o fator de estrutura dinâmica |
O trabalho específico do experimento fica aqui no caderno: preparação do estado fundamental (PRE) e o pós-processamento “ ” (POST). As duas etapas que exigem muitos recursos quânticos, a compactação e a execução, são executadas dentro da função.
Este tutorial é um complemento do artigo “Simulação da dispersão de nêutrons em materiais quânticos com circuitos quânticos”, que desenvolve o mesmo experimento passo a passo: o mesmo modelo KCuF, a preparação do estado fundamental, o impulso de nêutrons e o pós-processamento, com a síntese de Trotter, a compactação AQC e a execução mitigada descritas passo a passo. Leia esse tutorial para saber como funciona a compactação AQC. Leia este artigo para realizar o mesmo experimento por meio de um modelo de função implantado: o núcleo quântico se torna uma única chamada de função, e a compactação AQC, que leva várias horas, é executada dentro do worker do Serverless, em vez de na sua máquina; assim, você não precisa de um sistema HPC nem de manter o kernel aberto enquanto ela é executada. Essa mesma chamada também aciona outros experimentos de dinâmica do 1D.
Requisitos
Antes de iniciar este tutorial, certifique-se de ter o seguinte:
-
A função foi implantada na sua conta do Qiskit Serverless. Execute primeiro o modelo de função complementar: implante e execute o modelo de função AQC + dinâmica de Trotter. Esse guia explica passo a passo como obter os arquivos-fonte e enviar a função para a sua conta. Este tutorial apenas chama a função implantada.
-
IBM Quantum® credenciais salvas para
QiskitServerless(consulte o modelo de função). Ambos os exemplos deste tutorial chamam a função implantada; portanto, ambos precisam dela. -
Qiskit SDK v2.0 ou versão posterior (
pip install qiskit). -
O cliente do Catálogo do Qiskit IBM (
pip install qiskit-ibm-catalog). -
NumPy, SciPy, e Matplotlib (
pip install numpy scipy matplotlib). É necessário o SciPy 1.14 ou uma versão posterior para o otimizador COBYQA utilizado na preparação do estado fundamental. -
A pilha de redes tensoriais AQC, pois a preparação do estado fundamental na Etapa 1 é executada localmente neste notebook:
pip install 'qiskit-addon-aqc-tensor[quimb-jax]==0.3.1'.
A primeira chamada a uma função recém-implantada fica em espera enquanto o worker do Serverless instala suas dependências; portanto, é de se esperar uma latência adicional nessa execução.
Instalação
Importe as bibliotecas e defina os auxiliares específicos do experimento que serão utilizados posteriormente: build_gs_ansatz (o ansatz variacional do hamiltoniano, ou HVA, para a preparação do estado fundamental), prepare_ground_state (DMRG com maximização da fidelidade MPS) e get_spectrum, plot_green, e plot_spectrum (o pós-processamento “ ”). Essas informações foram adaptadas do tutorial original sobre espalhamento de nêutrons.
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.
Carregar o modelo de função
Acesse Qiskit Serverless e carregue o arquivo implantado aqc-dynamics-function. Ambos os exemplos deste tutorial chamam o mesmo fn handle, portanto, a função é carregada apenas uma vez, aqui.
# Credentials are read from the account saved once via QiskitServerless.save_account(...)
serverless = QiskitServerless()
fn = serverless.load("aqc-dynamics-function")Exemplo de simulador em pequena escala
Primeiramente, executamos o fluxo de trabalho completo em uma pequena rede de 10 lojas, utilizando exatamente o mesmo statevector backend. Isso valida o fluxo PRE → FUNCTION → POST antes de consumir qualquer tempo da QPU.
Etapa 1: Mapeie entradas clássicas para um problema quântico
Construa o hamiltoniano KCuF como um hamiltoniano de Heisenberg isotrópico ( SparsePauliOp com acoplamento em cada ligação entre vizinhos mais próximos; as cadeias são operadores de Pauli, de modo que fornece o acoplamento de spin ). Prepare o estado fundamental com DMRG e maximização da fidelidade MPS e, em seguida, incorpore o “neutron kick”: uma rotação no site central. O circuito preparado é o que passamos para a função como initial_state. Deixamos esse valor como observables padrão ( por local), que corresponde exatamente à leitura necessária para o fluxo de trabalho de nêutrons.
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.
Etapas 2 e 3: Compactar e executar com o modelo de função
Em um fluxo de trabalho feito manualmente, essas são duas etapas distintas: otimizar os circuitos para o hardware (Etapa 2) e executá-los (Etapa 3). O modelo de função agrupa ambas em uma única chamada. Ele realiza a síntese de Trotter, a compactação AQC e a transpilagem para hardware e, em seguida, executa os circuitos (aqui no simulador exato; posteriormente, com mitigação de erros integrada no hardware). Os dois parâmetros de ajuste são aqc_segments (o plano de compactação) e aqc_options (as configurações do MPS e do otimizador). Cada segmento comprime {"n_steps": k, "ansatz_steps": m} etapas consecutivas k do método de Trotter em um ansatz construído a partir de um alvo de Trotter de m-etapas, e quaisquer etapas além disso são sum(n_steps) executadas como o método de Trotter padrão. Os primeiros passos, com baixo entrelaçamento, podem ser bem comprimidos em um ansatz raso (ansatz_steps=1), portanto, neste caso, comprimimos os três primeiros passos em um ansatz de camada única e os dois seguintes em um ansatz mais profundo de duas camadas; os cinco passos restantes dos 10 passos de Trotter são executados como o método de Trotter padrão. Pois aqc_options seguimos o tutorial original: dimensão de ligação MPS max_bond=32, cutoff=1e-8, e um otimizador L-BFGS-B limitado a 100 iterações.
Chame a função carregada na função Setup. backend="statevector" executa o caminho de referência exato: sem tempo de QPU, com os circuitos sendo executados em um simulador de vetor de estado exato dentro do worker sem servidor (ainda é necessária uma conta Qiskit Serverless salva para chamá-lo). O representa initial_state o estado fundamental preparado (incluindo o kick); observables é omitido para que a função meça o padrão por site.
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)
Etapa 4: Realizar o pós-processamento e apresentar o resultado no formato clássico desejado
Aplicar a transformada de Fourier à função de Green para obter um e, aplicar simetria espelhada e cortar os valores negativos: o pós-processamento padrão de nêutrons. O espelhamento é exato porque para este modelo, e os valores negativos que permanecem são artefatos da transformada de Fourier de uma série temporal finita e amostrada discretamente; portanto, eles são cortados em zero. Nesta pequena simulação exata, o continuum de dois spinons é resolvido apenas de forma aproximada, mas o mecanismo é idêntico ao da simulação de hardware que se segue.
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:
Exemplo de hardware em grande escala
O mesmo fluxo de trabalho pode ser ampliado sem alterar nenhum código científico: uma cadeia de 30 nós, o dobro da profundidade de Trotter (20 passos), um plano de compressão que varia a profundidade do ansatz (um ansatz mais profundo para os passos posteriores, mais entrelaçados) e a execução em um processador IBM Quantum com a mitigação de erros integrada à função (desacoplamento dinâmico, giro de Pauli e extinção de erros por leitura girada (TREX)). Seguimos as mesmas quatro etapas do exemplo do simulador, reutilizando o fn handle da função Setup.
Coluna “ 1 ” | Pequena escala | Em grande escala |
|---|---|---|
| Qubits | 22 | 30 |
| Passos de trotador | 22 | 20 |
| Etapas com compressão AQC (1 camada + 2 camadas) | 3 + 2 = 5 | 6 + 4 = 10 |
| Camadas de ansatz do estado fundamental | 3 | 5 |
| Dimensão máxima da ligação do MPS | 32 | 128 |
| Back-end | statevector | QPU com DD, giro de Pauli e TREX |
Etapa 1: Mapeie entradas clássicas para um problema quântico
Construa o mesmo KCuF Heisenberg SparsePauliOp e prepare o estado fundamental, agora com um gs_layers=5 ansatz mais profundo para a cadeia mais longa; em seguida, aplique o impulso de nêutrons no sítio central. Isso é idêntico ao mapeamento em pequena escala, mas em .
É de se esperar uma fidelidade do estado fundamental menor do que na simulação de 10 nós: cerca de 0.82 neste caso, contra 0.98 para a cadeia menor, pois cinco camadas de HVA não conseguem capturar totalmente um estado fundamental de 30 nós. Isso é esperado, e não uma falha; além disso, o tutorial original aceita cerca de 0.65 em 50 sites pela mesma razão. Aumentar o limite de iterações do COBYQA melhora o desempenho, mas gs_layers acarreta um custo clássico adicional.
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.
Etapas 2 e 3: Compactar e executar com o modelo de função
A mesma chamada única do exemplo do simulador, agora com backend_name apontando para um processador IBM Quantum, de modo que a função seja compilada e executada nesse local. O plano de compressão varia a profundidade do ansatz: os seis primeiros passos de Trotter (de baixo entrelaçamento) são comprimidos em um ansatz raso de camada única; os quatro seguintes, em um ansatz mais profundo de duas camadas; e os 10 passos restantes, dos 20, são executados como o método de Trotter padrão. aqc_options aumenta a dimensão do grafo de ligações MPS para max_bond=128 na cadeia mais longa e mais entrelaçada (igual à original), mantendo o mesmo otimizador L-BFGS-B limitado a 100 iterações. Ative estimator_options a mitigação de erros integrada: desacoplamento dinâmico ( XY4 ), “gate twirling” e mitigação de medições TREX. Os valores padrão da função já correspondem ao tutorial original para todos esses itens, exceto pelo orçamento de aprendizado do TREX (measure_noise_learning). O bloco inteiro ainda é exibido porque um valor fornecido pelo chamador substitui estimator_options os valores padrão da função em sua totalidade, em vez de se fundir com eles; portanto, omitir uma chave faria com que o sistema recorresse ao valor padrão de IBM Quantum Compute, em vez do valor padrão da função.
# 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
A execução em grande escala não é rápida, e na maior parte do tempo é feita de forma clássica, em vez de na QPU. A compactação AQC é executada dentro da função antes que qualquer dado chegue à QPU: em 30 locais, max_bond=128 isso levou quase quatro horas em nossa execução, contra os cerca de 18 minutos de tempo de QPU indicados na estimativa de uso no início deste tutorial. Além disso, há o tempo de espera na fila. Você não precisa manter este notebook ou o kernel abertos enquanto ele estiver em execução.
Copie o ID do trabalho exibido na célula anterior e salve-o. As próximas três células permitem que você retome a corrida mais tarde:
- Reconecte-se, o que só é necessário em uma nova sessão do kernel: execute novamente as células de configuração para recriá-las e
serverless, em seguida, reconstrua ojobidentificador a partir do ID que você salvou. Ignore esta célula se você ainda estiver na sessão em que fez o envio, pois o identificador já está ativo. - Verificar status: execute novamente até que seja exibido o resultado
DONE. - Obter o resultado: execute somente quando o status for
DONE.
A célula de reconexão a seguir contém um marcador de posição. Substitua por sua própria versão 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}
Etapa 4: Realizar o pós-processamento e apresentar o resultado no formato clássico desejado
Pós-processamento idêntico ao da simulação: aplicar a transformada de Fourier à função de Green para obter um e, aplicar simetria espelhada e cortar os valores negativos. Com a cadeia mais longa e a evolução, o continuum de dois spinons é muito melhor resolvido. Deve preencher a faixa entre os limites tracejados, sendo mais brilhante próximo a .
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:
Apêndice
O exemplo de hardware anterior executa um único comprimento de cadeia. Os três espectros a seguir provêm de execuções anteriores desse mesmo fluxo de trabalho ibm_pittsburgh em 10, 20 e 30 locais, com todas as outras variáveis de entrada mantidas fixas: 20 passos de Trotter em dt = 0.6, o plano de compressão de seis passos de uma camada mais quatro de duas camadas com compressão AQC, e max_bond = 128. Esses são resultados registrados, e não valores provenientes das células anteriores.
As mesmas configurações são utilizadas nos três tamanhos, de modo que os espectros são diretamente comparáveis. Ajustá-los de acordo com o comprimento da cadeia, com mais camadas de ansatz do estado fundamental ou um valor maior de max_bond, por exemplo, pode proporcionar resultados melhores do que qualquer um dos apresentados aqui.



Próximas etapas
- Adapte esse fluxo de trabalho ao seu próprio sistema: a função aceita qualquer 1D vizinho mais próximo
SparsePauliOp, portanto, um hamiltoniano de cadeia, um estado inicial ou um conjunto de observáveis diferentes seguem o mesmo fluxo PRE → FUNÇÃO → POST. Consulte o contrato completo de entrada/saída no modelo da AQC Dynamics em GitHub. - Leia o artigo do qual este benchmark foi extraído: Lee et al., Avaliação comparativa da simulação quântica com experimentos de espalhamento de nêutrons ( arXiv:2603.15608 ).
- Em comparação com o tutorial original “Simular espalhamento de nêutrons”, este tutorial adapta o fluxo de trabalho embutido para um modelo de função implantado.
- Aprofunde-se nas técnicas de mitigação e supressão de erros aplicadas na execução em hardware: desacoplamento dinâmico, “Pauli twirling” e TREX.