Skip to main content
IBM Quantum Platform

Compilación cuántica aproximada para circuitos de evolución temporal

Tiempo estimado de ejecución: 15 segundos en un procesador Heron (NOTA: Se trata únicamente de una estimación). (El tiempo de ejecución puede variar.)


Resultados del aprendizaje

Una vez completado este tutorial, habrás adquirido los siguientes conocimientos:

  • Cómo utilizar el complemento AQC-Tensor de Qiskit para comprimir circuitos de Trotter profundos en circuitos de ansatz superficiales
  • Cómo generar un ansatz parametrizado a partir de un circuito de Trotter y optimizar sus parámetros mediante métodos de redes tensoriales (MPS)
  • Cómo evaluar la fidelidad de un circuito comprimido con respecto a la evolución objetivo y ejecutarlo en hardware cuántico

Requisitos previos

Se recomienda que te familiarices con estos temas:


En segundo plano

Este tutorial muestra cómo implementar la compilación cuántica aproximada mediante redes tensoriales (AQC-Tensor) con Qiskit para mejorar el rendimiento de los circuitos cuánticos. AQC-Tensor comprime los circuitos Trotter profundos en circuitos menos profundos y más compatibles con el hardware, al tiempo que mantiene la precisión de la simulación.

Cómo funciona AQC-Tensor

Consideremos la simulación de un hamiltoniano HH durante un tiempo total de tt utilizando kk pasos de Trotter. El circuito completo de Trotter es:

Ufull=[UTrotter(t/k)]kU_{\text{full}} = \left[U_{\text{Trotter}}(t/k)\right]^k

Un enfoque simplista utiliza pocos pasos de Trotter para que la profundidad del circuito sea manejable, pero esto da lugar a un error de Trotter considerable. AQC-Tensor resuelve esta tensión separando la precisión de la profundidad:

  1. Circuito de referencia (alta precisión, profundo): Construye un circuito de Trotter con muchos pasos —por ejemplo, 10k10k — para el mismo tiempo de evolución. Este circuito presenta un error de Trotter mucho menor, pero es demasiado complejo para implementarlo en hardware. Dado que solo se simula de forma clásica como un estado de producto matricial (MPS), la profundidad no supone ningún problema.

  2. Circuito de Ansatz (poca profundidad, parametrizado): Define un circuito parametrizado V(θ)V(\theta) con la misma estructura que un circuito de Trotter de un solo paso. Inicialízalo de modo que V(θinit)=UTrotter(t/k)V(\theta_{\text{init}}) = U_{\text{Trotter}}(t/k), y luego optimiza iterativamente θ\theta para que V(θ)V(\theta) reproduzca el estado objetivo de alta precisión con la mayor precisión posible.

El resultado es un circuito que conserva la profundidad de un solo paso de Trotter, pero alcanza la precisión de muchos, lo que lo hace viable para el hardware cuántico a corto plazo.

Cuándo utilizar AQC-Tensor

AQC-Tensor resulta más eficaz cuando:

  • La profundidad del circuito supera los tiempos de coherencia del hardware. Si una simulación de Trotter requiere más pasos de Trotter de los que el dispositivo puede admitir, AQC-Tensor puede comprimir la evolución en un circuito de menor profundidad.
  • El entrelazamiento sigue siendo manejable desde el punto de vista clásico. El entrelazamiento total en un estado evolucionado en el tiempo depende principalmente del tiempo de evolución tt, y no del número de pasos de Trotter kk. Esto significa que un circuito objetivo con 10k10k pasos no suele ser más difícil de representar como un MPS que uno con kk pasos, siempre y cuando tt sea lo suficientemente corto como para que las dimensiones de los enlaces sigan siendo manejables.
  • Existe un enfoque natural. Dado que el enfoque refleja la estructura de un circuito de Trotter, ofrece un punto de partida con base física y parámetros iniciales bien definidos, lo que permite evitar los problemas de convergencia que pueden afectar a los enfoques variacionales arbitrarios.

Este enfoque contrasta con la compresión de circuitos genérica: en lugar de intentar aproximar un operador unitario arbitrario con menos puertas lógicas, AQC-Tensor mantiene la misma estructura de puertas y optimiza sus parámetros para reducir el error de Trotter. Consulte la documentación de AQC-Tensor para obtener más información.

Este tutorial te guía a lo largo de todo el flujo de trabajo de preparación de estados de AQC-Tensor: definición de un hamiltoniano, generación de circuitos de Trotter, compresión de estos mediante optimización de redes tensoriales y ejecución del resultado en un hardware de tip IBM Quantum®.


Requisitos

Antes de empezar este tutorial, asegúrate de que tienes instalado lo siguiente:

  • Qiskit SDK v2.0 o posterior, con soporte para visualización
  • Qiskit Runtime v0.22 o posterior (pip install qiskit-ibm-runtime)
  • AQC-Tensor Qiskit addon (pip install 'qiskit-addon-aqc-tensor[aer,quimb-jax]')

Configuración

import numpy as np
import quimb.tensor
import datetime
import matplotlib.pyplot as plt

from scipy.linalg import expm
from scipy.optimize import OptimizeResult, minimize

from qiskit.quantum_info import SparsePauliOp, Pauli
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit import QuantumCircuit
from qiskit.synthesis import SuzukiTrotter

from qiskit_addon_utils.problem_generators import (
    generate_time_evolution_circuit,
)
from qiskit_addon_aqc_tensor.ansatz_generation import (
    generate_ansatz_from_circuit,
)
from qiskit_addon_aqc_tensor.objective import MaximizeStateFidelity
from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit
from qiskit_addon_aqc_tensor.simulation import compute_overlap

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime.fake_provider import FakeKyiv

from rustworkx.visualization import graphviz_draw

Ejemplo de simulador a pequeña escala

En esta sección se utiliza un sistema de 10 puntos para explicar paso a paso el flujo de trabajo de AQC-Tensor. Simulamos la dinámica de una cadena de espines XXZ de 10 nodos, un modelo ampliamente estudiado para analizar las interacciones de espín y las propiedades magnéticas.

El hamiltoniano es el siguiente:

H^XXZ=i=1L1Ji,(i+1)(XiX(i+1)+YiY(i+1)+2ZiZ(i+1)),\hat{\mathcal{H}}_{XXZ} = \sum_{i=1}^{L-1} J_{i,(i+1)}\left(X_i X_{(i+1)}+Y_i Y_{(i+1)}+ 2\cdot Z_i Z_{(i+1)} \right) \, ,

donde Ji,(i+1)J_{i,(i+1)} es un coeficiente aleatorio para el borde (i,i+1)(i, i+1) y L=10L=10.

Paso 1: Asignar entradas clásicas a un problema cuántico

En este paso, hacemos lo siguiente:

  1. Define el hamiltoniano, la observable y el estado inicial.
  2. Calcula el valor exacto de la esperanza matemática mediante el método clásico para poder compararlo más adelante.
  3. Genera un circuito de Trotter de alta precisión (el objetivo de AQC) y comprímelo en un ansatz de baja profundidad utilizando AQC-Tensor.

Establece el hamiltoniano, la observable y el estado inicial

# L is the number of sites in the 1D spin chain
L = 10

# Generate the coupling map
edge_list = [(i - 1, i) for i in range(1, L)]
even_edges = edge_list[::2]
odd_edges = edge_list[1::2]
coupling_map = CouplingMap(edge_list)

# Generate random coefficients for our XXZ Hamiltonian
np.random.seed(0)
Js = np.random.rand(L - 1) + 0.5 * np.ones(L - 1)
hamiltonian = SparsePauliOp(Pauli("I" * L))
for i, edge in enumerate(even_edges + odd_edges):
    hamiltonian += SparsePauliOp.from_sparse_list(
        [
            ("XX", (edge), Js[i] / 2),
            ("YY", (edge), Js[i] / 2),
            ("ZZ", (edge), Js[i]),
        ],
        num_qubits=L,
    )

# Generate a ZZ observable between the two middle qubits
observable = SparsePauliOp.from_sparse_list(
    [("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)

# Generate an initial Néel state |1010101010⟩
initial_state_circuit = QuantumCircuit(L)
for i in range(L):
    if i % 2:
        initial_state_circuit.x(i)

print("Hamiltonian:", hamiltonian)
print("Observable:", observable)
graphviz_draw(coupling_map.graph, method="circo")

Output:

Hamiltonian: SparsePauliOp(['IIIIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII', 'IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII'],
              coeffs=[1.        +0.j, 0.52440675+0.j, 0.52440675+0.j, 1.0488135 +0.j,
 0.60759468+0.j, 0.60759468+0.j, 1.21518937+0.j, 0.55138169+0.j,
 0.55138169+0.j, 1.10276338+0.j, 0.52244159+0.j, 0.52244159+0.j,
 1.04488318+0.j, 0.4618274 +0.j, 0.4618274 +0.j, 0.9236548 +0.j,
 0.57294706+0.j, 0.57294706+0.j, 1.14589411+0.j, 0.46879361+0.j,
 0.46879361+0.j, 0.93758721+0.j, 0.6958865 +0.j, 0.6958865 +0.j,
 1.391773  +0.j, 0.73183138+0.j, 0.73183138+0.j, 1.46366276+0.j])
Observable: SparsePauliOp(['IIIIZZIIII'],
              coeffs=[1.+0.j])
Output of the previous code cell

Calcular el valor exacto de la esperanza

Para un sistema de este tamaño, podemos calcular directamente el valor esperado a lo largo del tiempo utilizando la exponenciación de matrices. Esto nos sirve como referencia para evaluar la precisión del circuito AQC.

aqc_evolution_time = 0.2

# Each baseline Trotter step covers dt = aqc_evolution_time / 3
# The subsequent (uncompressed) step covers 1 additional dt
subsequent_evolution_time = aqc_evolution_time / 3
total_evolution_time = aqc_evolution_time + subsequent_evolution_time

# Compute exact expectation value via matrix exponentiation
H_matrix = hamiltonian.to_matrix()
U_exact = expm(-1j * H_matrix * total_evolution_time)

# Build the initial state vector (Néel state)
initial_state_vec = np.zeros(2**L)
state_idx = sum(2**i for i in range(L) if i % 2)
initial_state_vec[state_idx] = 1.0

# Evolve and compute expectation value
evolved_state = U_exact @ initial_state_vec
obs_matrix = observable.to_matrix()
exact_expval = (evolved_state.conj() @ obs_matrix @ evolved_state).real

print(f"AQC evolution time: {aqc_evolution_time}")
print(f"Subsequent evolution time: {subsequent_evolution_time:.6f}")
print(f"Total evolution time: {total_evolution_time:.6f}")
print(f"Exact expectation value: {exact_expval:.6f}")

Output:

AQC evolution time: 0.2
Subsequent evolution time: 0.066667
Total evolution time: 0.266667
Exact expectation value: -0.700899

Generar el circuito objetivo de AQC

Ahora construiremos el circuito de Trotter que servirá como objetivo del AQC. Este circuito utiliza numerosos pasos de Trotter (32) para garantizar una alta precisión. Dado que solo se simulará de forma clásica como un MPS —y no se ejecutará en hardware—, la gran profundidad no supone ningún problema.

aqc_target_num_trotter_steps = 32

aqc_target_circuit = initial_state_circuit.copy()
aqc_target_circuit.compose(
    generate_time_evolution_circuit(
        hamiltonian,
        synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
        time=aqc_evolution_time,
    ),
    inplace=True,
)

Generar un ansatz, los parámetros iniciales, el circuito resultante y un circuito de referencia

A continuación, construimos un circuito «bueno» con el mismo tiempo de evolución que el objetivo del AQC, pero con muchos menos pasos de Trotter (solo uno). Pasamos este circuito a generate_ansatz_from_circuit, que devuelve:

  1. Un circuito de aproximación general y parametrizado con la misma conectividad de dos qubits.
  2. Parámetros iniciales que reproducen el circuito de entrada cuando se conectan al ansatz.

También construimos:

  • Un circuito posterior con un paso de Trotter que se añadirá (sin comprimir) tras la parte optimizada con AQC, siguiendo el enfoque del tutorial sobre el estado inicial de AQC-Tensor.
  • Un circuito Trotter de referencia que utiliza cuatro pasos de Trotter a lo largo de todo el tiempo de evolución (aqc_evolution_time + subsequent_evolution_time). Esto sirve como punto de comparación: representa lo que se ejecutaría en hardware sin AQC. El enfoque AQC (3 pasos comprimidos + 1 paso sin comprimir) ofrece una mayor precisión a menor profundidad.
aqc_ansatz_num_trotter_steps = 1

aqc_good_circuit = initial_state_circuit.copy()
aqc_good_circuit.compose(
    generate_time_evolution_circuit(
        hamiltonian,
        synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),
        time=aqc_evolution_time,
    ),
    inplace=True,
)

aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(
    aqc_good_circuit
)

# Subsequent circuit: 1 non-compressed Trotter step appended after AQC
subsequent_num_trotter_steps = 1
subsequent_circuit = generate_time_evolution_circuit(
    hamiltonian,
    synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
    time=subsequent_evolution_time,
)

# Baseline Trotter circuit: 4 Trotter steps over total evolution time, no AQC
baseline_num_trotter_steps = 4
baseline_circuit = initial_state_circuit.copy()
baseline_circuit.compose(
    generate_time_evolution_circuit(
        hamiltonian,
        synthesis=SuzukiTrotter(reps=baseline_num_trotter_steps),
        time=total_evolution_time,
    ),
    inplace=True,
)

print(
    f"Target circuit:      depth {aqc_target_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
    f"Baseline circuit:    depth {baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({baseline_num_trotter_steps} Trotter steps, time={total_evolution_time:.4f})"
)
print(
    f"Subsequent circuit:  depth {subsequent_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({subsequent_num_trotter_steps} Trotter step, time={subsequent_evolution_time:.4f})"
)
print(
    f"Ansatz circuit:      depth {aqc_ansatz.depth(lambda x: x.operation.num_qubits == 2)}, with {len(aqc_initial_parameters)} parameters"
)
aqc_ansatz.draw("mpl", fold=-1)

Output:

Target circuit:      depth 384
Baseline circuit:    depth 48 (4 Trotter steps, time=0.2667)
Subsequent circuit:  depth 12 (1 Trotter step, time=0.0667)
Ansatz circuit:      depth 3, with 156 parameters
Output of the previous code cell

Configurar la simulación de redes tensoriales y generar el MPS deseado

Utilizamos el simulador de circuitos «Matrix-Product State» (MPS) de Quimb, y JAX proporciona la diferenciación automática para la optimización basada en gradientes. A continuación, construimos una representación MPS del estado objetivo y evaluamos la fidelidad inicial entre el ansatz inicial y el estado objetivo. Dado que la instancia del problema es un ejemplo relativamente pequeño, el nivel de fidelidad inicial es bastante alto.

simulator_settings = QuimbSimulator(
    quimb.tensor.CircuitMPS, autodiff_backend="jax"
)

aqc_target_mps = tensornetwork_from_circuit(
    aqc_target_circuit, simulator_settings
)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())

good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)
starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2
print(f"Starting fidelity: {starting_fidelity:.6f}")

Output:

Target MPS maximum bond dimension: 5
Starting fidelity: 0.998246

Optimizar los parámetros del ansatz

Minimizamos la MaximizeStateFidelity función de coste utilizando el optimizador L-BFGS-B. El optimizador ajusta de forma iterativa los parámetros del ansatz para maximizar la fidelidad entre el circuito del ansatz y el MPS objetivo.

aqc_stopping_fidelity = 1
aqc_max_iterations = 500

stopping_point = 1.0 - aqc_stopping_fidelity
objective = MaximizeStateFidelity(
    aqc_target_mps, aqc_ansatz, simulator_settings
)


def callback(intermediate_result: OptimizeResult):
    fidelity = 1 - intermediate_result.fun
    print(
        f"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}"
    )
    if intermediate_result.fun < stopping_point:
        raise StopIteration


result = minimize(
    objective,
    aqc_initial_parameters,
    method="L-BFGS-B",
    jac=True,
    options={"maxiter": aqc_max_iterations},
    callback=callback,
)
if result.status not in (0, 1, 99):
    raise RuntimeError(
        f"Optimization failed: {result.message} (status={result.status})"
    )

print(f"Done after {result.nit} iterations.")
aqc_final_parameters = result.x

Output:

2026-05-18 13:14:49.731596 Intermediate result: Fidelity 0.99952882
2026-05-18 13:14:49.734425 Intermediate result: Fidelity 0.99958531
2026-05-18 13:14:49.737101 Intermediate result: Fidelity 0.99960093
2026-05-18 13:14:49.739813 Intermediate result: Fidelity 0.99961046
2026-05-18 13:14:49.742969 Intermediate result: Fidelity 0.99962560
2026-05-18 13:14:49.745916 Intermediate result: Fidelity 0.99964395
2026-05-18 13:14:49.748615 Intermediate result: Fidelity 0.99968150
2026-05-18 13:14:49.753684 Intermediate result: Fidelity 0.99970569
2026-05-18 13:14:49.756208 Intermediate result: Fidelity 0.99973788
2026-05-18 13:14:49.759067 Intermediate result: Fidelity 0.99975385
2026-05-18 13:14:49.762321 Intermediate result: Fidelity 0.99976458
2026-05-18 13:14:49.765526 Intermediate result: Fidelity 0.99977661
2026-05-18 13:14:49.768496 Intermediate result: Fidelity 0.99978663
2026-05-18 13:14:49.771278 Intermediate result: Fidelity 0.99980236
2026-05-18 13:14:49.773735 Intermediate result: Fidelity 0.99981607
2026-05-18 13:14:49.776339 Intermediate result: Fidelity 0.99982811
2026-05-18 13:14:49.779177 Intermediate result: Fidelity 0.99985827
2026-05-18 13:14:49.782243 Intermediate result: Fidelity 0.99988354
2026-05-18 13:14:49.784904 Intermediate result: Fidelity 0.99991608
2026-05-18 13:14:49.787737 Intermediate result: Fidelity 0.99993336
2026-05-18 13:14:49.790414 Intermediate result: Fidelity 0.99993956
2026-05-18 13:14:49.793029 Intermediate result: Fidelity 0.99994421
2026-05-18 13:14:49.795585 Intermediate result: Fidelity 0.99994743
2026-05-18 13:14:49.835045 Intermediate result: Fidelity 0.99994791
2026-05-18 13:14:49.839786 Intermediate result: Fidelity 0.99994803
2026-05-18 13:14:49.842403 Intermediate result: Fidelity 0.99994898
2026-05-18 13:14:49.873779 Intermediate result: Fidelity 0.99994898
Done after 27 iterations.

Montar el circuito AQC definitivo

Una vez que disponemos de los parámetros optimizados, los aplicamos al ansatz y, a continuación, añadimos el paso de Trotter siguiente (sin comprimir). El circuito resultante tiene la profundidad de un solo paso de Trotter comprimido más un paso sin comprimir, pero la parte comprimida se aproxima a la precisión de 32 pasos de Trotter.

aqc_final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
aqc_final_circuit.compose(subsequent_circuit, inplace=True)
aqc_final_circuit.draw("mpl", fold=-1)

Output:

Output of the previous code cell

Paso 2: Optimizar el problema para la ejecución en hardware cuántico

En este ejemplo a pequeña escala, utilizamos un backend ficticio (FakeKyiv) para simular la ejecución en hardware a nivel local. Compilamos tanto el circuito optimizado con AQC (aqc_final_circuit) como el circuito Trotter de referencia (baseline_circuit, cuatro pasos de Trotter a lo largo de todo el tiempo de evolución, sin AQC) a la arquitectura del conjunto de instrucciones (ISA) del backend, con optimization_level=3 para reducir aún más la profundidad del circuito.

backend = FakeKyiv()

pass_manager = generate_preset_pass_manager(
    backend=backend, optimization_level=3
)

# Transpile the AQC-optimized circuit (compressed + subsequent step)
isa_circuit = pass_manager.run(aqc_final_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
print(
    "AQC circuit depth:",
    isa_circuit.depth(lambda x: x.operation.num_qubits == 2),
)

# Transpile the baseline Trotter circuit (no AQC optimization)
isa_baseline_circuit = pass_manager.run(baseline_circuit)
isa_baseline_observable = observable.apply_layout(isa_baseline_circuit.layout)
print(
    "Baseline Trotter circuit depth:",
    isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2),
)

Output:

AQC circuit depth: 15
Baseline Trotter circuit depth: 27

Paso 3: Ejecutar utilizando Qiskit primitives

Utilizamos la EstimatorV2 primitiva con el backend simulado para ejecutar tanto el circuito optimizado mediante AQC como el circuito de Trotter de referencia, midiendo la observable ZZ en cada caso.

estimator = Estimator(backend)

# Run both circuits
aqc_result = estimator.run([(isa_circuit, isa_observable)]).result()
baseline_result = estimator.run(
    [(isa_baseline_circuit, isa_baseline_observable)]
).result()

Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado

Extraemos los valores esperados de ambas ejecuciones y los comparamos con el resultado exacto. El circuito Trotter de referencia muestra el resultado que obtendríamos sin AQC con la misma profundidad de circuito, mientras que el circuito AQC ilustra la mejora conseguida gracias a la optimización de redes tensoriales.

aqc_expval = aqc_result[0].data.evs.tolist()
baseline_expval = baseline_result[0].data.evs.tolist()

print(f"Exact:              {exact_expval:.4f}")
print(
    f"Baseline Trotter:   {baseline_expval:.4f}, |\u0394| = {np.abs(exact_expval - baseline_expval):.4f}  (depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)}, {baseline_num_trotter_steps} steps)"
)
print(
    f"AQC (3+1):          {aqc_expval:.4f}, |\u0394| = {np.abs(exact_expval - aqc_expval):.4f}  (depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)}, compressed+subsequent)"
)

Output:

Exact:              -0.7009
Baseline Trotter:   -0.5400, |Δ| = 0.1609  (depth 27, 4 steps)
AQC (3+1):          -0.5728, |Δ| = 0.1281  (depth 15, compressed+subsequent)
plt.style.use("seaborn-v0_8")

labels = [
    f"Baseline Trotter\n({baseline_num_trotter_steps} steps, depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)})",
    f"AQC (3+1)\n(depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)})",
]
values = [baseline_expval, aqc_expval]
colors = ["tab:orange", "tab:blue"]

plt.figure(figsize=(8, 5))
bars = plt.bar(labels, values, color=colors, width=0.5)
plt.axhline(
    y=exact_expval,
    color="tab:green",
    linestyle="--",
    linewidth=2,
    label=f"Exact ({exact_expval:.4f})",
)
plt.ylabel("Expected Value")
plt.title(
    "AQC-Tensor (3 compressed + 1 uncompressed) vs Baseline Trotter (10-site XXZ)"
)
plt.legend()
for bar in bars:
    y_val = bar.get_height()
    plt.text(
        bar.get_x() + bar.get_width() / 2.0,
        y_val,
        f"{y_val:.4f}",
        ha="center",
        va="bottom" if y_val >= 0 else "top",
    )
plt.axhline(y=0, color="black", linewidth=0.3)
plt.tight_layout()
plt.show()

Output:

Output of the previous code cell

Ejemplo de hardware a gran escala

Ahora ampliamos el modelo XXZ a 50 sitios para demostrar el funcionamiento de AQC-Tensor con un problema de mayor envergadura. El proceso es el mismo que en el ejemplo a pequeña escala: comprimimos tres pasos de Trotter mediante AQC y añadimos un paso sin comprimir.

En un sistema de este tamaño, la exponenciación de matrices resulta inviable (dimensiones 2502^{50} ), por lo que calculamos el valor esperado de referencia directamente a partir de un MPS de alta precisión que se ha hecho evolucionar durante todo el tiempo.

Pasos 1 a 4 combinados

# -------------------------Step 1-------------------------

# Define the 50-site spin chain
L = 50
edge_list = [(i - 1, i) for i in range(1, L)]
even_edges = edge_list[::2]
odd_edges = edge_list[1::2]
coupling_map = CouplingMap(edge_list)

# Random XXZ Hamiltonian
np.random.seed(0)
Js = np.random.rand(L - 1) + 0.5 * np.ones(L - 1)
hamiltonian = SparsePauliOp(Pauli("I" * L))
for i, edge in enumerate(even_edges + odd_edges):
    hamiltonian += SparsePauliOp.from_sparse_list(
        [
            ("XX", (edge), Js[i] / 2),
            ("YY", (edge), Js[i] / 2),
            ("ZZ", (edge), Js[i]),
        ],
        num_qubits=L,
    )

observable = SparsePauliOp.from_sparse_list(
    [("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)

# Initial Néel state
initial_state_circuit = QuantumCircuit(L)
for i in range(L):
    if i % 2:
        initial_state_circuit.x(i)

# Time parameters
aqc_evolution_time = 0.2
subsequent_evolution_time = aqc_evolution_time / 3
total_evolution_time = aqc_evolution_time + subsequent_evolution_time

# AQC target circuit (high-accuracy, 32 Trotter steps for AQC portion)
aqc_target_num_trotter_steps = 32

aqc_target_circuit = initial_state_circuit.copy()
aqc_target_circuit.compose(
    generate_time_evolution_circuit(
        hamiltonian,
        synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
        time=aqc_evolution_time,
    ),
    inplace=True,
)

# Generate ansatz from 1-step Trotter circuit
aqc_good_circuit = initial_state_circuit.copy()
aqc_good_circuit.compose(
    generate_time_evolution_circuit(
        hamiltonian,
        synthesis=SuzukiTrotter(reps=1),
        time=aqc_evolution_time,
    ),
    inplace=True,
)

aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(
    aqc_good_circuit
)

# Subsequent circuit: 1 non-compressed Trotter step
subsequent_circuit = generate_time_evolution_circuit(
    hamiltonian,
    synthesis=SuzukiTrotter(reps=1),
    time=subsequent_evolution_time,
)

# Baseline Trotter circuit: 4 Trotter steps over total evolution time, no AQC
baseline_num_trotter_steps = 4
baseline_circuit = initial_state_circuit.copy()
baseline_circuit.compose(
    generate_time_evolution_circuit(
        hamiltonian,
        synthesis=SuzukiTrotter(reps=baseline_num_trotter_steps),
        time=total_evolution_time,
    ),
    inplace=True,
)
print(
    f"Target circuit:  depth {aqc_target_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
    f"Ansatz circuit:  depth {aqc_ansatz.depth(lambda x: x.operation.num_qubits == 2)}, with {len(aqc_initial_parameters)} parameters"
)
print(
    f"Subsequent circuit: depth {subsequent_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
    f"Baseline circuit:   depth {baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({baseline_num_trotter_steps} steps, time={total_evolution_time:.4f})"
)

# Build target MPS and compute reference expectation value
simulator_settings = QuimbSimulator(
    quimb.tensor.CircuitMPS, autodiff_backend="jax"
)
aqc_target_mps = tensornetwork_from_circuit(
    aqc_target_circuit, simulator_settings
)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())

# For the reference expectation value, we need the full evolution (AQC + subsequent)
# Build a high-accuracy full circuit for MPS reference
full_target_circuit = initial_state_circuit.copy()
full_target_circuit.compose(
    generate_time_evolution_circuit(
        hamiltonian,
        synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
        time=total_evolution_time,
    ),
    inplace=True,
)
full_target_mps = tensornetwork_from_circuit(
    full_target_circuit, simulator_settings
)
exact_expval = full_target_mps.local_expectation(
    quimb.pauli("Z") & quimb.pauli("Z"), (L // 2 - 1, L // 2)
).real.item()
print(f"Reference expectation value (from MPS): {exact_expval:.6f}")

# Optimize ansatz parameters
objective = MaximizeStateFidelity(
    aqc_target_mps, aqc_ansatz, simulator_settings
)


def callback(intermediate_result: OptimizeResult):
    fidelity = 1 - intermediate_result.fun
    print(
        f"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}"
    )


result = minimize(
    objective,
    aqc_initial_parameters,
    method="L-BFGS-B",
    jac=True,
    options={"maxiter": 500},
    callback=callback,
)
if result.status not in (0, 1, 99):
    raise RuntimeError(
        f"Optimization failed: {result.message} (status={result.status})"
    )
print(f"Done after {result.nit} iterations.")

# Assemble the final AQC circuit: optimized ansatz + subsequent Trotter step
aqc_final_circuit = aqc_ansatz.assign_parameters(result.x)
aqc_final_circuit.compose(subsequent_circuit, inplace=True)

# -------------------------Step 2-------------------------

service = QiskitRuntimeService()
backend = service.least_busy(min_num_qubits=127)
print(backend)

pass_manager = generate_preset_pass_manager(
    backend=backend, optimization_level=3
)
isa_circuit = pass_manager.run(aqc_final_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
print(
    "AQC circuit depth:",
    isa_circuit.depth(lambda x: x.operation.num_qubits == 2),
)

# Also transpile the baseline Trotter circuit (4 Trotter steps, no AQC)
isa_baseline_circuit = pass_manager.run(baseline_circuit)
isa_baseline_observable = observable.apply_layout(isa_baseline_circuit.layout)
print(
    "Baseline Trotter circuit depth:",
    isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2),
)

# -------------------------Step 3-------------------------

# Submit both circuits in a single job
estimator = Estimator(backend)
estimator.options.environment.job_tags = ["TUT_AQCTE"]

job = estimator.run(
    [
        (isa_circuit, isa_observable),
        (isa_baseline_circuit, isa_baseline_observable),
    ]
)
print("Job ID:", job.job_id())

Output:

Target circuit:  depth 385
Ansatz circuit:  depth 7, with 816 parameters
Subsequent circuit: depth 12
Baseline circuit:   depth 49 (4 steps, time=0.2667)
Target MPS maximum bond dimension: 5
Reference expectation value (from MPS): -0.738669
2026-05-18 13:02:11.219150 Intermediate result: Fidelity 0.99795732
2026-05-18 13:02:11.232256 Intermediate result: Fidelity 0.99822481
2026-05-18 13:02:11.245160 Intermediate result: Fidelity 0.99829520
2026-05-18 13:02:11.257765 Intermediate result: Fidelity 0.99832379
2026-05-18 13:02:11.270280 Intermediate result: Fidelity 0.99836416
2026-05-18 13:02:11.284116 Intermediate result: Fidelity 0.99840073
2026-05-18 13:02:11.296856 Intermediate result: Fidelity 0.99846863
2026-05-18 13:02:11.309602 Intermediate result: Fidelity 0.99865244
2026-05-18 13:02:11.322012 Intermediate result: Fidelity 0.99872665
2026-05-18 13:02:11.334195 Intermediate result: Fidelity 0.99892335
2026-05-18 13:02:11.346570 Intermediate result: Fidelity 0.99901045
2026-05-18 13:02:11.359202 Intermediate result: Fidelity 0.99907181
2026-05-18 13:02:11.371511 Intermediate result: Fidelity 0.99911125
2026-05-18 13:02:11.383870 Intermediate result: Fidelity 0.99918585
2026-05-18 13:02:11.396184 Intermediate result: Fidelity 0.99921504
2026-05-18 13:02:11.408543 Intermediate result: Fidelity 0.99924936
2026-05-18 13:02:11.422557 Intermediate result: Fidelity 0.99929226
2026-05-18 13:02:11.436275 Intermediate result: Fidelity 0.99933099
2026-05-18 13:02:11.449511 Intermediate result: Fidelity 0.99935792
2026-05-18 13:02:11.462093 Intermediate result: Fidelity 0.99937925
2026-05-18 13:02:11.475783 Intermediate result: Fidelity 0.99940690
2026-05-18 13:02:11.490254 Intermediate result: Fidelity 0.99944409
2026-05-18 13:02:11.503292 Intermediate result: Fidelity 0.99946840
2026-05-18 13:02:11.516064 Intermediate result: Fidelity 0.99949378
2026-05-18 13:02:11.532861 Intermediate result: Fidelity 0.99951380
2026-05-18 13:02:11.546182 Intermediate result: Fidelity 0.99955313
2026-05-18 13:02:11.559168 Intermediate result: Fidelity 0.99955707
2026-05-18 13:02:11.571753 Intermediate result: Fidelity 0.99959306
2026-05-18 13:02:11.584257 Intermediate result: Fidelity 0.99960486
2026-05-18 13:02:11.597610 Intermediate result: Fidelity 0.99961714
2026-05-18 13:02:11.610106 Intermediate result: Fidelity 0.99962953
2026-05-18 13:02:11.622515 Intermediate result: Fidelity 0.99963525
2026-05-18 13:02:11.635543 Intermediate result: Fidelity 0.99964658
2026-05-18 13:02:11.649044 Intermediate result: Fidelity 0.99965027
2026-05-18 13:02:11.664148 Intermediate result: Fidelity 0.99965802
2026-05-18 13:02:11.678033 Intermediate result: Fidelity 0.99966731
2026-05-18 13:02:11.692714 Intermediate result: Fidelity 0.99967780
2026-05-18 13:02:11.706753 Intermediate result: Fidelity 0.99968567
2026-05-18 13:02:11.720780 Intermediate result: Fidelity 0.99969139
2026-05-18 13:02:11.733471 Intermediate result: Fidelity 0.99969628
2026-05-18 13:02:11.745998 Intermediate result: Fidelity 0.99970331
2026-05-18 13:02:11.758424 Intermediate result: Fidelity 0.99970796
2026-05-18 13:02:11.771986 Intermediate result: Fidelity 0.99971165
2026-05-18 13:02:11.785841 Intermediate result: Fidelity 0.99971892
2026-05-18 13:02:11.799105 Intermediate result: Fidelity 0.99972226
2026-05-18 13:02:11.811623 Intermediate result: Fidelity 0.99972441
2026-05-18 13:02:11.824114 Intermediate result: Fidelity 0.99972679
2026-05-18 13:02:11.837179 Intermediate result: Fidelity 0.99972965
2026-05-18 13:02:12.345479 Intermediate result: Fidelity 0.99972965
Done after 49 iterations.
<IBMBackend('ibm_pittsburgh')>
AQC circuit depth: 71
Baseline Trotter circuit depth: 111
Job ID: d85kc6o0bvlc73d5nhn0
# -------------------------Step 4-------------------------

hw_results = job.result()
aqc_expval = hw_results[0].data.evs.tolist()
baseline_expval = hw_results[1].data.evs.tolist()

print(f"Exact (MPS):        {exact_expval:.4f}")
print(
    f"Baseline Trotter:   {baseline_expval:.4f}, |\u0394| = {np.abs(exact_expval - baseline_expval):.4f}"
)
print(
    f"AQC (3+1):          {aqc_expval:.4f}, |\u0394| = {np.abs(exact_expval - aqc_expval):.4f}"
)

labels = [
    f"Baseline Trotter\n({baseline_num_trotter_steps} steps, depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)})",
    f"AQC (3+1)\n(depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)})",
]
values = [baseline_expval, aqc_expval]
colors = ["tab:orange", "tab:blue"]

plt.figure(figsize=(8, 5))
bars = plt.bar(labels, values, color=colors, width=0.5)
plt.axhline(
    y=exact_expval,
    color="tab:green",
    linestyle="--",
    linewidth=2,
    label=f"Exact ({exact_expval:.4f})",
)
plt.ylabel("Expected Value")
plt.title(
    "AQC-Tensor (3 compressed + 1 uncompressed) vs Baseline Trotter (50-site XXZ)"
)
plt.legend()
for bar in bars:
    y_val = bar.get_height()
    plt.text(
        bar.get_x() + bar.get_width() / 2.0,
        y_val,
        f"{y_val:.4f}",
        ha="center",
        va="bottom" if y_val >= 0 else "top",
    )
plt.axhline(y=0, color="black", linewidth=0.3)
plt.tight_layout()
plt.show()

Output:

Exact (MPS):        -0.7387
Baseline Trotter:   -0.5955, |Δ| = 0.1432
AQC (3+1):          -0.6734, |Δ| = 0.0653
Output of the previous code cell

Próximos pasos

Recomendaciones

Si te ha parecido interesante este trabajo, quizá te interese el siguiente material:

¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.