Compilation quantique approximative pour circuits d'évolution temporelle
Estimation du temps d'exécution : 15 secondes sur un processeur Heron (REMARQUE : il s'agit uniquement d'une estimation.) (Votre temps d'exécution peut varier.)
Résultats d'apprentissage
À l'issue de ce tutoriel, vous devriez être en mesure de comprendre les points suivants :
- Comment utiliser le module complémentaire AQC-Tensor pour Qiskit afin de réduire des circuits de Trotter profonds en circuits d'ansatz peu profonds
- Comment générer une approximation paramétrée à partir d'un circuit de Trotter et optimiser ses paramètres à l'aide de méthodes de réseaux de tenseurs (MPS)
- Comment évaluer la fidélité d'un circuit compressé par rapport à l'évolution cible et l'exécuter sur un matériel quantique
Prérequis
Nous vous recommandons de vous familiariser avec les sujets suivants :
- Les bases des circuits quantiques
- Simulation hamiltonienne et trotterisation
- Introduction aux primitives
Arrière-plan
Ce tutoriel explique comment mettre en œuvre la compilation quantique approximative à l'aide de réseaux de tenseurs (AQC-Tensor) avec Qiskit afin d'améliorer les performances des circuits quantiques. AQC-Tensor compresse les circuits de Trotter profonds en circuits moins profonds et mieux adaptés au matériel, tout en préservant la précision de la simulation.
Comment fonctionne AQC-Tensor
Envisageons de simuler une é hamiltonienne pendant une durée totale de , en utilisant des pas de Trotter de . Le circuit complet de Trotter est le suivant :
Une approche naïve utilise un nombre réduit d'étapes de Trotter afin de maintenir la profondeur du circuit à un niveau raisonnable, mais cela entraîne une erreur de Trotter importante. AQC-Tensor résout ce dilemme en dissociant la précision de la profondeur :
-
Circuit cible (haute précision, profond) : construire un circuit de Trotter comportant de nombreux étages — par exemple, — pour un temps d'évolution identique. Ce circuit présente une erreur de Trotter bien moindre, mais il est trop complexe pour être mis en œuvre sur un circuit intégré. Comme elle n'est simulée de manière classique que sous la forme d'un état de produit matriciel (MPS), la profondeur n'est pas un problème.
-
Circuit d'Ansatz (faible profondeur, paramétré) : Définit un circuit d' paramétré présentant la même structure qu'un circuit de Trotter à un pas. Initialisez-le de manière à ce que , puis optimisez de manière itérative afin que reproduise l'état cible de haute précision aussi fidèlement que possible.
On obtient ainsi un circuit qui conserve la profondeur d'une seule étape de Trotter tout en atteignant la précision de plusieurs étapes, ce qui le rend viable pour le matériel quantique à court terme.
Quand utiliser AQC-Tensor
AQC-Tensor est particulièrement efficace lorsque :
- La profondeur du circuit dépasse les temps de cohérence du matériel. Si une simulation de Trotter nécessite plus d'étapes de Trotter que ce que l'appareil peut prendre en charge, AQC-Tensor peut compresser l'évolution en un circuit moins profond.
- L'intrication reste gérable d'un point de vue classique. L'intrication totale dans un état ayant évolué dans le temps dépend principalement de la durée d'évolution , et non du nombre d'étapes de Trotter . Cela signifie qu'un circuit cible comportant étapes n'est généralement pas plus difficile à représenter sous forme de MPS qu'un circuit comportant étapes, à condition que soit suffisamment court pour que les dimensions des liaisons restent gérables.
- Il existe une approche naturelle. Comme cette approche reflète la structure d'un circuit de Trotter, elle offre un point de départ fondé sur la physique avec des paramètres initiaux bien définis, ce qui permet d'éviter les problèmes de convergence qui peuvent affecter les approches variationnelles arbitraires.
Cette approche se distingue de la compression de circuits générique : plutôt que de chercher à approximer un opérateur unitaire arbitraire avec moins de portes logiques, AQC-Tensor conserve la même structure de portes et optimise ses paramètres afin de réduire l'erreur de Trotter. Pour plus d'informations, consultez la documentation d'AQC-Tensor.
Ce tutoriel vous guide tout au long du processus complet de préparation d'états avec AQC-Tensor : définition d'un hamiltonien, génération de circuits de Trotter, compression de ceux-ci via l'optimisation par réseau de tenseurs, et exécution du résultat sur le matériel d' IBM Quantum®.
Exigences
Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :
- Qiskit SDK v2.0 ou version ultérieure, avec prise en charge de la visualisation
- Qiskit Runtime v0.22 ou plus tard (
pip install qiskit-ibm-runtime) - AQC-Tensor Qiskit addon (
pip install 'qiskit-addon-aqc-tensor[aer,quimb-jax]')
Configuration
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_drawExemple de simulateur à petite échelle
Cette section utilise un système à 10 sites pour illustrer étape par étape le déroulement du processus AQC-Tensor. Nous simulons la dynamique d'une chaîne de spins XXZ à 10 sites, un modèle largement étudié pour l'analyse des interactions de spin et des propriétés magnétiques.
L'hamiltonien s'écrit comme suit :
où est un coefficient aléatoire pour l' de l'arête et .
Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique
À cette étape, nous :
- Définissez l'hamiltonien, l'observable et l'état initial.
- Calculez la valeur attendue exacte de manière classique afin de pouvoir la comparer ultérieurement.
- Générez un circuit de Trotter de haute précision (la cible AQC) et compressez-le en un ansatz de faible profondeur à l'aide d'AQC-Tensor.
Définir l'hamiltonien, l'observable et l'état initial
# 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])
Calculer la valeur exacte de l'espérance
Pour un système de cette taille, nous pouvons calculer directement la valeur attendue évolutive exacte à l'aide de l'exponentiation matricielle. Cela nous sert de référence pour évaluer la précision du circuit 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
Générer le circuit cible AQC
Nous allons maintenant construire le circuit de Trotter qui servira de cible pour l'AQC. Ce circuit utilise de nombreux étages de Trotter (32) pour garantir une grande précision. Comme elle ne sera simulée que de manière classique en tant que MPS — et non exécutée sur du matériel —, sa grande profondeur ne pose pas de problème.
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,
)Générer une hypothèse de départ, des paramètres initiaux, un circuit dérivé et un circuit de référence
Nous construisons ensuite un « bon » circuit ayant le même temps d'évolution que la cible de l'AQC, mais comportant beaucoup moins d'étapes de Trotter (une seule). Nous transmettons ce circuit à generate_ansatz_from_circuit, qui renvoie :
- Un circuit de référence général et paramétré présentant la même connectivité à deux qubits.
- Paramètres initiaux permettant de reproduire le circuit d'entrée une fois connecté à l'ansatz.
Nous construisons également :
- Un circuit supplémentaire comportant une étape de Trotter qui sera ajouté (sans compression) après la partie optimisée par AQC, conformément à l'approche présentée dans le tutoriel sur l'état initial d'AQC-Tensor.
- Un circuit de référence de Trotter utilisant quatre étapes de Trotter sur toute la durée de l'évolution (
aqc_evolution_time + subsequent_evolution_time). Cela sert de référence : il s'agit de ce que l'on obtiendrait sur du matériel sans AQC. L'approche AQC (3 étapes de compression + 1 étape sans compression) offre une meilleure précision à une profondeur moindre.
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
Configurer la simulation par réseau de tenseurs et construire le MPS cible
Nous utilisons le simulateur de circuits MPS (Matrix-Product State) de Quimb, JAX assurant la différenciation automatique pour l'optimisation par gradient. Nous construisons ensuite une représentation MPS de l'état cible et évaluons la fidélité initiale entre l'hypothèse de départ et l'état cible. Comme l'instance en question est un exemple relativement simple, le niveau de fidélité de départ est assez élevé.
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
Optimiser les paramètres de l'ansatz
Nous minimisons la MaximizeStateFidelity fonction de coût à l'aide de l'optimiseur L-BFGS-B. L'optimiseur ajuste de manière itérative les paramètres de l'ansatz afin de maximiser la fidélité entre le circuit de l'ansatz et le MPS cible.
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.xOutput:
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.
Assembler le circuit AQC final
Une fois les paramètres optimisés en main, nous les appliquons à l'ansatz, puis nous ajoutons l'étape de Trotter suivante (non compressée). Le circuit obtenu a la profondeur d'un seul pas de Trotter compressé plus un pas non compressé, mais la partie compressée offre une précision équivalente à celle de 32 pas 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:
Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique
Pour cet exemple à petite échelle, nous utilisons un backend fictif (FakeKyiv) afin de simuler l'exécution matérielle en local. Nous transpilons à la fois le circuit optimisé par AQC (aqc_final_circuit) et le circuit Trotter de référence (baseline_circuit, quatre étapes de Trotter sur toute la durée de l'évolution, sans AQC) vers l'architecture du jeu d'instructions (ISA) du backend, avec optimization_level=3 afin de réduire davantage la profondeur du circuit.
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
Étape 3 : Exécutez à l'aide d' Qiskit primitives
Nous utilisons la EstimatorV2 primitive avec le backend fictif pour exécuter à la fois le circuit optimisé par AQC et le circuit de référence de Trotter, en mesurant l'observable ZZ pour chacun d'eux.
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()Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité
Nous extrayons les valeurs attendues des deux séries de simulations et les comparons au résultat exact. Le circuit Trotter de référence montre ce que l'on obtiendrait sans AQC à la même profondeur de circuit, tandis que le circuit AQC illustre l'amélioration apportée par l'optimisation par réseau de tenseurs.
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:
Exemple de matériel à grande échelle
Nous passons désormais à un modèle XXZ à 50 sites afin de présenter AQC-Tensor sur un problème d'une ampleur plus réaliste. La procédure est la même que dans l'exemple à petite échelle : nous compressons trois étapes de Trotter à l'aide de l'AQC et ajoutons une étape non compressée.
Pour un système de cette taille, l'exponentiation matricielle n'est pas réalisable (dimensions ), nous calculons donc la valeur attendue de référence directement à partir d'un MPS de haute précision simulé sur toute la durée.
Étapes 1 à 4 combinées
# -------------------------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
Etapes suivantes
Si ce travail vous a paru intéressant, les ressources suivantes pourraient vous intéresser :
- Documentation de l'extension AQC-Tensor — comprend la technique AQC unitaire associée, qui optimise des circuits paramétrés afin d'approximer un opérateur unitaire cible plutôt qu'un état préparé
- Techniques d'atténuation et de suppression des erreurs
- Combiner les techniques d'atténuation des erreurs