Compilation quantique approximative pour les circuits d'évolution temporelle
Estimation d'utilisation : cinq minutes sur un processeur Eagle (REMARQUE : il s'agit uniquement d'une estimation. Votre temps d'exécution peut varier.)
Contexte
Ce tutoriel démontre comment implémenter la Compilation Quantique Approximative à l'aide de réseaux de tenseurs (AQC-Tensor) avec Qiskit pour améliorer les performances des circuits quantiques. Nous appliquons AQC-Tensor dans le cadre d'une évolution temporelle trotterisée afin de réduire la profondeur des circuits tout en maintenant la précision de la simulation, en suivant le cadre Qiskit pour la préparation et l'optimisation des états. Vous apprendrez à créer un circuit ansatz de faible profondeur à partir d'un circuit de Trotter initial, à l'optimiser avec des réseaux de tenseurs et à le préparer pour l'exécution sur du matériel quantique.
L'objectif principal est de simuler l'évolution temporelle d'un hamiltonien modèle avec une profondeur de circuit réduite. Ceci est réalisé grâce à l'addon Qiskit AQC-Tensor, qiskit-addon-aqc-tensor, qui exploite les réseaux de tenseurs, en particulier les états produits de matrices (MPS), pour compresser et optimiser le circuit initial. Par des ajustements itératifs, le circuit ansatz compressé maintient la fidélité par rapport au circuit original tout en restant réalisable sur le matériel quantique actuel. Plus de détails sont disponibles dans la documentation correspondante avec un exemple simple pour débuter.
La Compilation Quantique Approximative est particulièrement avantageuse dans les simulations quantiques qui dépassent les temps de cohérence du matériel, car elle permet de réaliser des simulations complexes de manière plus efficace. Ce tutoriel vous guide à travers la mise en place du flux de travail AQC-Tensor dans Qiskit, couvrant l'initialisation d'un hamiltonien, la génération de circuits de Trotter et la transpilation du circuit optimisé final pour un dispositif cible.
Prérequis
Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :
- Qiskit SDK v1.0 ou ultérieur, avec le support de visualisation
- Qiskit Runtime v0.22 ou ultérieur (
pip install qiskit-ibm-runtime) - Addon Qiskit AQC-Tensor (
pip install 'qiskit-addon-aqc-tensor[aer,quimb-jax]')
Configuration
# Added by doQumentation — installs packages not in the Binder environment
%pip install -q qiskit-addon-aqc-tensor quimb
import numpy as np
import quimb.tensor
import datetime
import matplotlib.pyplot as plt
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 rustworkx.visualization import graphviz_draw
Partie I. Exemple à petite échelle
La première partie de ce tutoriel utilise un exemple à petite échelle avec 10 sites pour illustrer le processus de correspondance d'un problème de simulation quantique vers un circuit quantique exécutable. Ici, nous explorerons la dynamique d'un modèle XXZ à 10 sites, ce qui nous permet de construire et d'optimiser un circuit quantique de taille gérable avant de passer à des systèmes plus grands.
Le modèle XXZ est largement étudié en physique pour examiner les interactions de spin et les propriétés magnétiques. Nous configurons le hamiltonien avec des conditions aux limites ouvertes et des interactions dépendantes du site entre les sites voisins le long de la chaîne.
Hamiltonien modèle et observable
Le hamiltonien pour notre modèle XXZ à 10 sites est défini comme :
où est un coefficient aléatoire correspondant à l'arête , et est le nombre de sites.
En simulant l'évolution de ce système avec une profondeur de circuit réduite, nous pouvons obtenir des informations sur l'utilisation d'AQC-Tensor pour compresser et optimiser les circuits.
Configuration du hamiltonien et de l'observable
Avant de formuler notre problème, nous devons configurer la carte de couplage, le hamiltonien et l'observable pour le modèle XXZ à 10 sites.
# L is the number of sites, also the length of the 1D spin chain
L = 10
# Generate the coupling map
edge_list = [(i - 1, i) for i in range(1, L)]
# Generate an edge-coloring so we can make hw-efficient circuits
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
)
print("Hamiltonian:", hamiltonian)
print("Observable:", observable)
graphviz_draw(coupling_map.graph, method="circo")
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])
Avec le hamiltonien défini, nous pouvons procéder à la construction de l'état initial.
# Generate an initial state
initial_state = QuantumCircuit(L)
for i in range(L):
if i % 2:
initial_state.x(i)
Étape 1 : Formuler les entrées classiques en un problème quantique
Maintenant que nous avons construit le hamiltonien, définissant les interactions spin-spin et les champs magnétiques externes qui caractérisent le système, nous suivons trois étapes principales dans le flux de travail AQC-Tensor :
- Générer le circuit AQC optimisé : En utilisant la trotterisation, nous approximons l'évolution initiale, qui est ensuite compressée pour réduire la profondeur du circuit.
- Créer le circuit d'évolution temporelle restant : Capturer l'évolution pour le temps restant au-delà du segment initial.
- Combiner les circuits : Fusionner le circuit AQC optimisé avec le circuit d'évolution restant en un circuit d'évolution temporelle complet prêt à être exécuté.
Cette approche crée un ansatz de faible profondeur pour l'évolution cible, permettant une simulation efficace dans les contraintes du matériel quantique actuel.
Déterminer la portion d'évolution temporelle à simuler classiquement
Notre objectif est de simuler l'évolution temporelle du hamiltonien modèle défini précédemment en utilisant l'évolution de Trotter. Pour rendre ce processus efficace pour le matériel quantique, nous divisons l'évolution en deux segments :
-
Segment initial : Cette portion initiale de l'évolution, de à , est simulable avec les MPS et peut être efficacement « compilée » à l'aide d'AQC-Tensor. En utilisant l'addon Qiskit AQC-Tensor, nous générons un circuit compressé pour ce segment, appelé
aqc_target_circuit. Comme ce segment sera simulé sur un simulateur de réseaux de tenseurs, nous pouvons nous permettre d'utiliser un nombre plus élevé de couches de Trotter sans impact significatif sur les ressources matérielles. Nous fixonsaqc_target_num_trotter_steps = 32pour ce segment. -
Segment suivant : Cette portion restante de l'évolution, de à , sera exécutée sur le matériel quantique, appelée
subsequent_circuit. Compte tenu des limitations du matériel, nous cherchons à utiliser le moins de couches de Trotter possible pour maintenir une profondeur de circuit gérable. Pour ce segment, nous utilisonssubsequent_num_trotter_steps = 3.
Choisir le temps de séparation
Nous choisissons comme temps de séparation pour équilibrer la simulabilité classique avec la faisabilité matérielle. Au début de l'évolution, l'intrication dans le modèle XXZ reste suffisamment faible pour que les méthodes classiques comme les MPS puissent l'approximer avec précision.
Lors du choix d'un temps de séparation, une bonne ligne directrice est de sélectionner un point où l'intrication est encore gérable classiquement mais capture suffisamment de l'évolution pour simplifier la portion exécutée sur le matériel. Des essais et erreurs peuvent être nécessaires pour trouver le meilleur équilibre pour différents hamiltoniens.
# Generate the AQC target circuit (initial segment)
aqc_evolution_time = 0.2
aqc_target_num_trotter_steps = 32
aqc_target_circuit = initial_state.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 the subsequent circuit
subsequent_num_trotter_steps = 3
subsequent_evolution_time = 0.2
subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
time=subsequent_evolution_time,
)
subsequent_circuit.draw("mpl", fold=-1)

Pour permettre une comparaison significative, nous allons générer deux circuits supplémentaires :
-
Circuit de comparaison AQC : Ce circuit évolue jusqu'à
aqc_evolution_timemais utilise la même durée de pas de Trotter que lesubsequent_circuit. Il sert de comparaison avec leaqc_target_circuit, montrant l'évolution que nous observerions sans utiliser un nombre accru de pas de Trotter. Nous appellerons ce circuitaqc_comparison_circuit. -
Circuit de référence : Ce circuit est utilisé comme référence pour obtenir le résultat exact. Il simule l'évolution complète à l'aide de réseaux de tenseurs pour calculer le résultat exact, fournissant une référence pour évaluer l'efficacité d'AQC-Tensor. Nous appellerons ce circuit
reference_circuit.
# Generate the AQC comparison circuit
aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps
/ subsequent_evolution_time
* aqc_evolution_time
)
print(
"Number of Trotter steps for comparison:",
aqc_comparison_num_trotter_steps,
)
aqc_comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)
Number of Trotter steps for comparison: 3
# Generate the reference circuit
evolution_time = 0.4
reps = 200
reference_circuit = initial_state.copy()
reference_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=reps),
time=evolution_time,
),
inplace=True,
)
Générer un ansatz et des paramètres initiaux à partir d'un circuit de Trotter avec moins de pas
Maintenant que nous avons construit nos quatre circuits, poursuivons avec le flux de travail AQC-Tensor. Tout d'abord, nous construisons un « bon » circuit qui a le même temps d'évolution que le circuit cible, mais avec moins de pas de Trotter (et donc moins de couches).
Ensuite, nous passons ce « bon » circuit à la fonction generate_ansatz_from_circuit d'AQC-Tensor. Cette fonction analyse la connectivité à deux qubits du circuit et renvoie deux éléments :
- Un circuit ansatz général et paramétré ayant la même connectivité à deux qubits que le circuit d'entrée.
- Des paramètres qui, lorsqu'ils sont injectés dans l'ansatz, reproduisent le circuit (bon) d'entrée.
Nous allons bientôt prendre ces paramètres et les ajuster itérativement pour rapprocher le circuit ansatz autant que possible du MPS cible.
aqc_ansatz_num_trotter_steps = 1
aqc_good_circuit = initial_state.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
)
aqc_ansatz.draw("mpl", fold=-1)

print(f"AQC Comparison circuit: depth {aqc_comparison_circuit.depth()}")
print(f"Target circuit: depth {aqc_target_circuit.depth()}")
print(
f"Ansatz circuit: depth {aqc_ansatz.depth()}, with {len(aqc_initial_parameters)} parameters"
)
AQC Comparison circuit: depth 36
Target circuit: depth 385
Ansatz circuit: depth 7, with 156 parameters
Choisir les paramètres pour la simulation par réseau de tenseurs
Ici, nous utilisons le simulateur de circuits à état produit de matrices de Quimb, accompagné de jax pour fournir le gradient.
simulator_settings = QuimbSimulator(
quimb.tensor.CircuitMPS, autodiff_backend="jax"
)
Ensuite, nous construisons une représentation MPS de l'état cible qui sera approximé à l'aide d'AQC-Tensor. Cette représentation permet une gestion efficace de l'intrication, fournissant une description compacte de l'état quantique pour l'optimisation ultérieure.
aqc_target_mps = tensornetwork_from_circuit(
aqc_target_circuit, simulator_settings
)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())
# Obtains the reference MPS, where we can obtain the exact expectation value by examining the `local_expectation``
reference_mps = tensornetwork_from_circuit(
reference_circuit, simulator_settings
)
reference_expval = reference_mps.local_expectation(
quimb.pauli("Z") & quimb.pauli("Z"), (L // 2 - 1, L // 2)
).real.item()
print("Reference MPS maximum bond dimension:", reference_mps.psi.max_bond())
Target MPS maximum bond dimension: 5
Reference MPS maximum bond dimension: 7
Notez qu'en choisissant un nombre plus élevé de pas de Trotter pour l'état cible, nous avons effectivement réduit son erreur de Trotter par rapport au circuit initial. Nous pouvons évaluer la fidélité () entre l'état préparé par le circuit initial et l'état cible pour quantifier cette différence.
good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)
starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2
print("Starting fidelity:", starting_fidelity)
Starting fidelity: 0.9982464959067222
Optimiser les paramètres de l'ansatz à l'aide de calculs MPS
Dans cette étape, nous optimisons les paramètres de l'ansatz en minimisant une fonction de coût simple, MaximizeStateFidelity, à l'aide de l'optimiseur L-BFGS de SciPy. Nous sélectionnons un critère d'arrêt pour la fidélité qui garantit qu'elle dépasse la fidélité du circuit initial sans AQC-Tensor. Une fois ce seuil atteint, le circuit compressé présentera à la fois une erreur de Trotter plus faible et une profondeur réduite par rapport au circuit original. En utilisant du temps CPU supplémentaire, l'optimisation peut se poursuivre pour augmenter davantage la fidélité.
# Setting values for the optimization
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:
# Good enough for now
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,
)
): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration
raise RuntimeError(
f"Optimization failed: {result.message} (status={result.status})"
)
print(f"Done after {result.nit} iterations.")
aqc_final_parameters = result.x
2025-04-14 11:46:52.174235 Intermediate result: Fidelity 0.99795851
2025-04-14 11:46:52.218249 Intermediate result: Fidelity 0.99822826
2025-04-14 11:46:52.280924 Intermediate result: Fidelity 0.99829675
2025-04-14 11:46:52.356214 Intermediate result: Fidelity 0.99832474
2025-04-14 11:46:52.411609 Intermediate result: Fidelity 0.99836131
2025-04-14 11:46:52.453747 Intermediate result: Fidelity 0.99839954
2025-04-14 11:46:52.496184 Intermediate result: Fidelity 0.99846517
2025-04-14 11:46:52.542046 Intermediate result: Fidelity 0.99865029
2025-04-14 11:46:52.583679 Intermediate result: Fidelity 0.99872332
2025-04-14 11:46:52.628732 Intermediate result: Fidelity 0.99892359
2025-04-14 11:46:52.690386 Intermediate result: Fidelity 0.99900640
2025-04-14 11:46:52.759398 Intermediate result: Fidelity 0.99907169
2025-04-14 11:46:52.819496 Intermediate result: Fidelity 0.99911423
2025-04-14 11:46:52.884505 Intermediate result: Fidelity 0.99918716
2025-04-14 11:46:52.947919 Intermediate result: Fidelity 0.99921278
2025-04-14 11:46:53.012808 Intermediate result: Fidelity 0.99924853
2025-04-14 11:46:53.083626 Intermediate result: Fidelity 0.99928797
2025-04-14 11:46:53.153235 Intermediate result: Fidelity 0.99933028
2025-04-14 11:46:53.221371 Intermediate result: Fidelity 0.99935757
2025-04-14 11:46:53.286211 Intermediate result: Fidelity 0.99938140
2025-04-14 11:46:53.352391 Intermediate result: Fidelity 0.99940964
2025-04-14 11:46:53.420472 Intermediate result: Fidelity 0.99944051
2025-04-14 11:46:53.486279 Intermediate result: Fidelity 0.99946828
2025-04-14 11:46:53.552338 Intermediate result: Fidelity 0.99948723
2025-04-14 11:46:53.618688 Intermediate result: Fidelity 0.99951011
2025-04-14 11:46:53.690878 Intermediate result: Fidelity 0.99954718
2025-04-14 11:46:53.762725 Intermediate result: Fidelity 0.99956267
2025-04-14 11:46:53.829784 Intermediate result: Fidelity 0.99958949
2025-04-14 11:46:53.897477 Intermediate result: Fidelity 0.99960498
2025-04-14 11:46:53.954633 Intermediate result: Fidelity 0.99961308
2025-04-14 11:46:54.010125 Intermediate result: Fidelity 0.99962894
2025-04-14 11:46:54.064717 Intermediate result: Fidelity 0.99964121
2025-04-14 11:46:54.118892 Intermediate result: Fidelity 0.99964348
2025-04-14 11:46:54.183236 Intermediate result: Fidelity 0.99964860
2025-04-14 11:46:54.245521 Intermediate result: Fidelity 0.99965695
2025-04-14 11:46:54.305792 Intermediate result: Fidelity 0.99966398
2025-04-14 11:46:54.355819 Intermediate result: Fidelity 0.99967816
2025-04-14 11:46:54.409580 Intermediate result: Fidelity 0.99968293
2025-04-14 11:46:54.457979 Intermediate result: Fidelity 0.99968936
2025-04-14 11:46:54.505891 Intermediate result: Fidelity 0.99969223
2025-04-14 11:46:54.551084 Intermediate result: Fidelity 0.99970009
2025-04-14 11:46:54.601817 Intermediate result: Fidelity 0.99970724
2025-04-14 11:46:54.650097 Intermediate result: Fidelity 0.99970987
2025-04-14 11:46:54.714727 Intermediate result: Fidelity 0.99971237
2025-04-14 11:46:54.780052 Intermediate result: Fidelity 0.99971916
2025-04-14 11:46:54.871994 Intermediate result: Fidelity 0.99971940
2025-04-14 11:46:54.958244 Intermediate result: Fidelity 0.99972465
2025-04-14 11:46:55.011057 Intermediate result: Fidelity 0.99972763
2025-04-14 11:46:55.175339 Intermediate result: Fidelity 0.99972894
2025-04-14 11:46:56.688912 Intermediate result: Fidelity 0.99972894
Done after 50 iterations.
parameters = [float(param) for param in aqc_final_parameters]
print("Final parameters:", parameters)
Final parameters: [-7.853983035039254, 1.5707966468427772, 1.5707962768868613, -1.570798010835122, 1.570794480409574, 1.5707972214146968, -1.570796593027083, 1.5707968206822998, -1.5707959018046258, -1.5707991700969144, 1.5707965852600927, 4.712386891737442, -7.853980840717957, 1.5707967508132654, 1.5707943162503217, -1.5707955382023582, 1.5707958007156742, 1.570796096113293, -1.5707928509846847, 1.5707971042943747, -1.570797909276557, -1.5707941020637393, 1.5707980179540793, 4.712389823219363, -1.5707928752386107, 1.5707996426312891, -1.5707975640471001, -1.570794132802984, 1.5707944361599957, 4.712390747060803, 0.1048818190315936, 0.06686710468840577, -0.0668645844756557, -3.1415923537135466, 1.2374931269696063, 6.323169390432535e-07, 3.53229204771738e-08, 2.1091105688681484, 6.283186439944202, 0.12152258846156239, 0.07961752617254866, -0.07961775088604585, -1.6564278051174865e-06, 2.0771163596472384, 3.141592651630471, -6.283185775192653, 1.7691609006726954, 3.1415922910116216, 0.19837572065074083, 0.11114901449078964, -0.11115124544944892, -3.141591983034976, 0.8570788408766729, 4.201601390404146e-07, -3.141593736550978, 0.34652010942396333, 6.283186232785291, 0.13606356527241956, 0.03891676349289617, -0.03891524189533726, -1.5707965732853424, 1.5707968967088564, -0.3086133992238162, 1.5707957152428194, 1.5707968398959653, -0.32062737993080026, 0.11027416939993417, 0.0726167290795046, -0.07262020423334464, -2.3729431959735024e-06, 1.8204437429254703, 9.299060301196612e-07, -3.141592899563451, 2.103269568939461, 3.1415937539734626, 0.11536891854817125, 0.09099022308254198, -0.09098864958606581, -3.1415913307373127, 2.078429034357281, -1.509777998069368e-06, -3.1415922600663255, 1.5189162645358172, -3.1415878461323583, 0.09999070991480716, 0.04352011445148391, -0.04351849541849812, -1.570797642506462, 1.570795238023824, 0.8903442644396505, 1.5707962698006606, 1.5707946765132268, 0.9098791754570567, 0.10448284343424026, 0.07317037684936827, -0.07316718173961152, -3.141592682240966, 2.1665363080039612, -7.450882112394189e-07, -5.771181304929921e-07, 2.615334999517103, -3.1415914971653898, 0.1890887078648001, 0.13578163074571992, -0.13578078143610256, 7.156734195912883e-07, 1.7915385305413096, -5.188866034727312e-07, 1.2827742939197711e-06, 1.2348316581417487, 6.28318357406372, 0.08061187643781703, 0.03820789039271876, -0.03820731868804904, 1.5707964027727628, 1.570798734462218, 4.387336153720882, -1.570795722044763, 1.570798457375325, 4.450361734163248, 0.092360147257953, 0.06047700345049011, -0.06048592856713045, -3.141591214829027, 2.6593289993286047, -2.366937342261038e-07, 8.112162974032695e-08, 1.8907014631413432, 8.355881261853104e-07, 0.23303641819370874, 0.14331998953606456, -0.1433194488304741, -3.141591621822901, 0.7455776479558791, 3.1415914520163586, -3.1415933560496105, 0.7603938554148255, -1.6230983177616282e-06, 0.07186349688535713, 0.03197144517771341, -0.031971177878588546, -4.712389048748508, 1.5707948403165752, 1.2773619319829186, -1.5707990802172127, 1.5707957676951863, 1.289083769394045, 0.13644999397718796, 0.032761460443590046, -0.032762060585195645, -1.5707977610073176, 1.5707964181578042, -3.4826435600366983, -4.712389691708343, 1.570794277502252, 2.799088046133275]
À ce stade, il suffit de trouver les paramètres finaux pour le circuit ansatz. Nous pouvons ensuite fusionner le circuit AQC optimisé avec le circuit d'évolution restant pour créer un circuit d'évolution temporelle complet destiné à l'exécution sur le matériel quantique.
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)

Nous devons également fusionner notre aqc_comparison_circuit avec le circuit d'évolution restant. Ce circuit sera utilisé pour comparer les performances du circuit optimisé par AQC-Tensor avec le circuit original.
aqc_comparison_circuit.compose(subsequent_circuit, inplace=True)
aqc_comparison_circuit.draw("mpl", fold=-1)

Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique
Sélectionnez le matériel. Ici, nous utiliserons l'un des dispositifs IBM Quantum® disponibles possédant au moins 127 qubits.
service = QiskitRuntimeService()
backend = service.least_busy(min_num_qubits=127)
print(backend)
Nous transpilons les PUBs (circuit et observables) pour correspondre à l'ISA (Instruction Set Architecture) du backend. En définissant optimization_level=3, le transpileur optimise le circuit pour s'adapter à une chaîne unidimensionnelle de qubits, réduisant ainsi le bruit qui affecte la fidélité du circuit. Une fois les circuits transformés dans un format compatible avec le backend, nous appliquons une transformation correspondante aux observables pour garantir leur alignement avec la disposition modifiée des qubits.
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("Observable info:", isa_observable)
print("Circuit depth:", isa_circuit.depth())
isa_circuit.draw("mpl", fold=-1, idle_wires=False)
Observable info: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ'],
coeffs=[1.+0.j])
Circuit depth: 111

Effectuez la transpilation pour le circuit de comparaison.
isa_comparison_circuit = pass_manager.run(aqc_comparison_circuit)
isa_comparison_observable = observable.apply_layout(
isa_comparison_circuit.layout
)
print("Observable info:", isa_comparison_observable)
print("Circuit depth:", isa_comparison_circuit.depth())
isa_comparison_circuit.draw("mpl", fold=-1, idle_wires=False)
Observable info: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ'],
coeffs=[1.+0.j])
Circuit depth: 158

Étape 3 : Exécuter à l'aide des primitives Qiskit
Dans cette étape, nous exécutons le circuit transpilé sur du matériel quantique (ou un backend simulé). En utilisant la classe EstimatorV2 de qiskit_ibm_runtime, nous configurons un estimateur pour exécuter le circuit et mesurer l'observable spécifié. Le résultat du job fournit la valeur attendue pour l'observable, nous donnant un aperçu des performances du circuit sur le matériel cible.
estimator = Estimator(backend)
job = estimator.run([(isa_circuit, isa_observable)])
print("Job ID:", job.job_id())
job.result()
Job ID: czyhqdxd8drg008hx0yg
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(), dtype=float64>), stds=np.ndarray(<shape=(), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(), dtype=float64>)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})
Effectuez l'exécution pour le circuit de comparaison.
job_comparison = estimator.run([(isa_comparison_circuit, isa_observable)])
print("Job Comparison ID:", job.job_id())
job_comparison.result()
Job Comparison ID: czyhqdxd8drg008hx0yg
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(), dtype=float64>), stds=np.ndarray(<shape=(), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(), dtype=float64>)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})
Étape 4 : Post-traiter et retourner le résultat dans le format classique souhaité
Dans ce cas, la reconstruction n'est pas nécessaire. Nous pouvons directement examiner le résultat en accédant à la valeur d'espérance à partir de la sortie d'exécution.
# AQC results
hw_results = job.result()
hw_results_dicts = [pub_result.data.__dict__ for pub_result in hw_results]
hw_expvals = [
pub_result_data["evs"].tolist() for pub_result_data in hw_results_dicts
]
aqc_expval = hw_expvals[0]
# AQC comparison results
hw_comparison_results = job_comparison.result()
hw_comparison_results_dicts = [
pub_result.data.__dict__ for pub_result in hw_comparison_results
]
hw_comparison_expvals = [
pub_result_data["evs"].tolist()
for pub_result_data in hw_comparison_results_dicts
]
aqc_compare_expval = hw_comparison_expvals[0]
print(f"Exact: \t{reference_expval:.4f}")
print(
f"AQC: \t{aqc_expval:.4f}, |∆| = {np.abs(reference_expval- aqc_expval):.4f}"
)
print(
f"AQC Comparison:\t{aqc_compare_expval:.4f}, |∆| = {np.abs(reference_expval- aqc_compare_expval):.4f}"
)
Exact: -0.5252
AQC: -0.4903, |∆| = 0.0349
AQC Comparison: 0.5424, |∆| = 1.0676
Diagramme en barres pour comparer les résultats des circuits AQC, de comparaison et exact.
plt.style.use("seaborn-v0_8")
labels = ["AQC Result", "AQC Comparison Result"]
values = [abs(aqc_expval), abs(aqc_compare_expval)]
plt.figure(figsize=(10, 6))
bars = plt.bar(labels, values, color=["tab:blue", "tab:purple"])
plt.axhline(
y=abs(reference_expval), color="red", linestyle="--", label="Exact Result"
)
plt.xlabel("Results")
plt.ylabel("Absolute Expected Value")
plt.title("AQC Result vs AQC Comparison Result (Absolute Values)")
plt.legend()
for bar in bars:
y_val = bar.get_height()
plt.text(
bar.get_x() + bar.get_width() / 2.0,
y_val,
round(y_val, 2),
va="bottom",
)
plt.show()
Partie II : passage à l'échelle
La deuxième partie de ce tutoriel s'appuie sur l'exemple précédent en passant à un système plus grand de 50 sites, illustrant comment transposer des problèmes de simulation quantique plus complexes en circuits quantiques exécutables. Ici, nous explorons la dynamique d'un modèle XXZ à 50 sites, ce qui nous permet de construire et d'optimiser un circuit quantique conséquent reflétant des tailles de systèmes plus réalistes.
Le hamiltonien de notre modèle XXZ à 50 sites est défini comme suit :
où est un coefficient aléatoire correspondant à l'arête , et est le nombre de sites. Définissez la carte de couplage et les arêtes pour le hamiltonien.
L = 50 # L = length of our 1D spin chain
# Generate the edge list for this spin-chain
edge_list = [(i - 1, i) for i in range(1, L)]
# Generate an edge-coloring so we can make hw-efficient circuits
even_edges = edge_list[::2]
odd_edges = edge_list[1::2]
# Instantiate a CouplingMap object
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,
)
observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
# Generate an initial state
L = hamiltonian.num_qubits
initial_state = QuantumCircuit(L)
for i in range(L):
if i % 2:
initial_state.x(i)
Étape 1 : Transposer les entrées classiques en un problème quantique
Pour ce problème plus grand, nous commençons par construire le hamiltonien du modèle XXZ à 50 sites, en définissant les interactions spin-spin et les champs magnétiques externes sur tous les sites. Ensuite, nous suivons trois étapes principales :
- Générer le circuit AQC optimisé : Utilisez la trottérisation pour approximer l'évolution initiale, puis compressez ce segment afin de réduire la profondeur du circuit.
- Créer le circuit d'évolution temporelle restant : Capturez l'évolution temporelle restante au-delà du segment initial.
- Combiner les circuits : Fusionnez le circuit AQC optimisé avec le circuit d'évolution restant pour créer un circuit d'évolution temporelle complet prêt à l'exécution. Générez le circuit cible AQC (le segment initial).
aqc_evolution_time = 0.2
aqc_target_num_trotter_steps = 32
aqc_target_circuit = initial_state.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érez le circuit subséquent (le segment restant).
subsequent_num_trotter_steps = 3
subsequent_evolution_time = 0.2
subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
time=subsequent_evolution_time,
)
Générez le circuit de comparaison AQC (le segment initial, mais avec le même nombre d'étapes de Trotter que le circuit subséquent).
# Generate the AQC comparison circuit
aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps
/ subsequent_evolution_time
* aqc_evolution_time
)
print(
"Number of Trotter steps for comparison:",
aqc_comparison_num_trotter_steps,
)
aqc_comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)
Number of Trotter steps for comparison: 3
Générez le circuit de référence.
evolution_time = 0.4
reps = 200
reference_circuit = initial_state.copy()
reference_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=reps),
time=evolution_time,
),
inplace=True,
)
Générez un ansatz et des paramètres initiaux à partir d'un circuit de Trotter avec moins d'étapes.
aqc_ansatz_num_trotter_steps = 1
aqc_good_circuit = initial_state.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
)
print(f"AQC Comparison circuit: depth {aqc_comparison_circuit.depth()}")
print(f"Target circuit: depth {aqc_target_circuit.depth()}")
print(
f"Ansatz circuit: depth {aqc_ansatz.depth()}, with {len(aqc_initial_parameters)} parameters"
)
AQC Comparison circuit: depth 36
Target circuit: depth 385
Ansatz circuit: depth 7, with 816 parameters
Définissez les paramètres pour la simulation par réseau de tenseurs, puis construisez une représentation en état produit matriciel (MPS) de l'état cible pour l'optimisation. Ensuite, évaluez la fidélité entre le circuit initial et l'état cible pour quantifier la différence d'erreur de Trotter.
simulator_settings = QuimbSimulator(
quimb.tensor.CircuitMPS, autodiff_backend="jax"
)
# Build the matrix-product representation of the state to be approximated by AQC
aqc_target_mps = tensornetwork_from_circuit(
aqc_target_circuit, simulator_settings
)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())
# Obtains the reference MPS, where we can obtain the exact expectation value by examining the `local_expectation``
reference_mps = tensornetwork_from_circuit(
reference_circuit, simulator_settings
)
reference_expval = reference_mps.local_expectation(
quimb.pauli("Z") & quimb.pauli("Z"), (L // 2 - 1, L // 2)
).real.item()
# Compute the starting fidelity
good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)
starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2
print("Starting fidelity:", starting_fidelity)
Target MPS maximum bond dimension: 5
Starting fidelity: 0.9926466919924161
Pour optimiser les paramètres de l'ansatz, nous minimisons la fonction de coût MaximizeStateFidelity à l'aide de l'optimiseur L-BFGS de SciPy, avec un critère d'arrêt défini pour dépasser la fidélité du circuit initial sans AQC-Tensor. Cela garantit que le circuit compressé présente à la fois une erreur de Trotter plus faible et une profondeur réduite.
# Setting values for the optimization
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:
# Good enough for now
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,
)
): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration
raise RuntimeError(
f"Optimization failed: {result.message} (status={result.status})"
)
print(f"Done after {result.nit} iterations.")
aqc_final_parameters = result.x
2025-04-14 11:48:28.705807 Intermediate result: Fidelity 0.99795851
2025-04-14 11:48:28.743265 Intermediate result: Fidelity 0.99822826
2025-04-14 11:48:28.776629 Intermediate result: Fidelity 0.99829675
2025-04-14 11:48:28.816153 Intermediate result: Fidelity 0.99832474
2025-04-14 11:48:28.856437 Intermediate result: Fidelity 0.99836131
2025-04-14 11:48:28.896432 Intermediate result: Fidelity 0.99839954
2025-04-14 11:48:28.936670 Intermediate result: Fidelity 0.99846517
2025-04-14 11:48:28.982069 Intermediate result: Fidelity 0.99865029
2025-04-14 11:48:29.026130 Intermediate result: Fidelity 0.99872332
2025-04-14 11:48:29.067426 Intermediate result: Fidelity 0.99892359
2025-04-14 11:48:29.110742 Intermediate result: Fidelity 0.99900640
2025-04-14 11:48:29.161362 Intermediate result: Fidelity 0.99907169
2025-04-14 11:48:29.207933 Intermediate result: Fidelity 0.99911423
2025-04-14 11:48:29.266772 Intermediate result: Fidelity 0.99918716
2025-04-14 11:48:29.331727 Intermediate result: Fidelity 0.99921278
2025-04-14 11:48:29.401694 Intermediate result: Fidelity 0.99924853
2025-04-14 11:48:29.467980 Intermediate result: Fidelity 0.99928797
2025-04-14 11:48:29.533281 Intermediate result: Fidelity 0.99933028
2025-04-14 11:48:29.600833 Intermediate result: Fidelity 0.99935757
2025-04-14 11:48:29.670816 Intermediate result: Fidelity 0.99938140
2025-04-14 11:48:29.736928 Intermediate result: Fidelity 0.99940964
2025-04-14 11:48:29.802931 Intermediate result: Fidelity 0.99944051
2025-04-14 11:48:29.869177 Intermediate result: Fidelity 0.99946828
2025-04-14 11:48:29.940156 Intermediate result: Fidelity 0.99948723
2025-04-14 11:48:30.005751 Intermediate result: Fidelity 0.99951011
2025-04-14 11:48:30.070853 Intermediate result: Fidelity 0.99954718
2025-04-14 11:48:30.139171 Intermediate result: Fidelity 0.99956267
2025-04-14 11:48:30.210506 Intermediate result: Fidelity 0.99958949
2025-04-14 11:48:30.279647 Intermediate result: Fidelity 0.99960498
2025-04-14 11:48:30.348016 Intermediate result: Fidelity 0.99961308
2025-04-14 11:48:30.414311 Intermediate result: Fidelity 0.99962894
2025-04-14 11:48:30.488910 Intermediate result: Fidelity 0.99964121
2025-04-14 11:48:30.561298 Intermediate result: Fidelity 0.99964348
2025-04-14 11:48:30.632214 Intermediate result: Fidelity 0.99964860
2025-04-14 11:48:30.705703 Intermediate result: Fidelity 0.99965695
2025-04-14 11:48:30.775679 Intermediate result: Fidelity 0.99966398
2025-04-14 11:48:30.842629 Intermediate result: Fidelity 0.99967816
2025-04-14 11:48:30.912357 Intermediate result: Fidelity 0.99968293
2025-04-14 11:48:30.979420 Intermediate result: Fidelity 0.99968936
2025-04-14 11:48:31.049196 Intermediate result: Fidelity 0.99969223
2025-04-14 11:48:31.125391 Intermediate result: Fidelity 0.99970009
2025-04-14 11:48:31.201256 Intermediate result: Fidelity 0.99970724
2025-04-14 11:48:31.272424 Intermediate result: Fidelity 0.99970987
2025-04-14 11:48:31.338907 Intermediate result: Fidelity 0.99971237
2025-04-14 11:48:31.404800 Intermediate result: Fidelity 0.99971916
2025-04-14 11:48:31.475226 Intermediate result: Fidelity 0.99971940
2025-04-14 11:48:31.547746 Intermediate result: Fidelity 0.99972465
2025-04-14 11:48:31.622827 Intermediate result: Fidelity 0.99972763
2025-04-14 11:48:31.819516 Intermediate result: Fidelity 0.99972894
2025-04-14 11:48:33.444538 Intermediate result: Fidelity 0.99972894
Done after 50 iterations.
parameters = [float(param) for param in aqc_final_parameters]
Construisez le circuit final pour la transpilation en assemblant l'ansatz optimisé avec le circuit d'évolution temporelle restant.
aqc_final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
aqc_final_circuit.compose(subsequent_circuit, inplace=True)
aqc_comparison_circuit.compose(subsequent_circuit, inplace=True)
Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique
Sélectionnez le backend.
service = QiskitRuntimeService()
backend = service.least_busy(min_num_qubits=127)
print(backend)
Transpilez le circuit complété sur le matériel cible, en le préparant pour l'exécution. Le circuit ISA résultant peut ensuite être envoyé pour exécution sur le 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("Observable info:", isa_observable)
print("Circuit depth:", isa_circuit.depth())
isa_circuit.draw("mpl", fold=-1, idle_wires=False)
Observable info: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
Circuit depth: 122

isa_comparison_circuit = pass_manager.run(aqc_comparison_circuit)
isa_comparison_observable = observable.apply_layout(
isa_comparison_circuit.layout
)
print("Observable info:", isa_comparison_observable)
print("Circuit depth:", isa_comparison_circuit.depth())
isa_comparison_circuit.draw("mpl", fold=-1, idle_wires=False)
Observable info: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
Circuit depth: 158

Étape 3 : Exécuter à l'aide des primitives Qiskit
Dans cette étape, nous exécutons le circuit transpilé sur du matériel quantique (ou un backend simulé) en utilisant EstimatorV2 de qiskit_ibm_runtime pour mesurer l'observable spécifié. Le résultat de la tâche fournira des informations précieuses sur les performances du circuit sur le matériel cible.
Pour cet exemple à plus grande échelle, nous explorerons comment utiliser EstimatorOptions pour mieux gérer et contrôler les paramètres de notre expérience matérielle. Bien que ces réglages soient optionnels, ils sont utiles pour suivre les paramètres de l'expérience et affiner les options d'exécution afin d'obtenir des résultats optimaux.
Pour une liste complète des options d'exécution disponibles, consultez la documentation qiskit-ibm-runtime.
twirling_options = {
"enable_gates": True,
"enable_measure": True,
"num_randomizations": 300,
"shots_per_randomization": 100,
"strategy": "active",
}
zne_options = {
"amplifier": "gate_folding",
"noise_factors": [1, 2, 3],
"extrapolated_noise_factors": list(np.linspace(0, 3, 31)),
"extrapolator": ["exponential", "linear", "fallback"],
}
meas_learning_options = {
"num_randomizations": 512,
"shots_per_randomization": 512,
}
resilience_options = {
"measure_mitigation": True,
"zne_mitigation": True,
"zne": zne_options,
"measure_noise_learning": meas_learning_options,
}
estimator_options = {
"resilience": resilience_options,
"twirling": twirling_options,
}
estimator = Estimator(backend, options=estimator_options)
job = estimator.run([(isa_circuit, isa_observable)])
print("Job ID:", job.job_id())
job.result()
Job ID: czyjx6crxz8g008f63r0
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(), dtype=float64>), stds=np.ndarray(<shape=(), dtype=float64>), evs_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), stds_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), ensemble_stds_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), evs_extrapolated=np.ndarray(<shape=(3, 31), dtype=float64>), stds_extrapolated=np.ndarray(<shape=(3, 31), dtype=float64>)), metadata={'shots': 30000, 'target_precision': 0.005773502691896258, 'circuit_metadata': {}, 'resilience': {'zne': {'extrapolator': 'exponential'}}, 'num_randomizations': 300})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': True, 'enable_measure': True, 'num_randomizations': 300, 'shots_per_randomization': 100, 'interleave_randomizations': True, 'strategy': 'active'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': True, 'pec_mitigation': False, 'zne': {'noise_factors': [1, 2, 3], 'extrapolator': ['exponential', 'linear', 'fallback'], 'extrapolated_noise_factors': [0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5, 1.6, 1.7000000000000002, 1.8, 1.9000000000000001, 2, 2.1, 2.2, 2.3000000000000003, 2.4000000000000004, 2.5, 2.6, 2.7, 2.8000000000000003, 2.9000000000000004, 3]}}, 'version': 2})
job_comparison = estimator.run([(isa_comparison_circuit, isa_observable)])
print("Job Comparison ID:", job.job_id())
job_comparison.result()
Job Comparison ID: czyjx6crxz8g008f63r0
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(), dtype=float64>), stds=np.ndarray(<shape=(), dtype=float64>), evs_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), stds_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), ensemble_stds_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), evs_extrapolated=np.ndarray(<shape=(3, 31), dtype=float64>), stds_extrapolated=np.ndarray(<shape=(3, 31), dtype=float64>)), metadata={'shots': 30000, 'target_precision': 0.005773502691896258, 'circuit_metadata': {}, 'resilience': {'zne': {'extrapolator': 'exponential'}}, 'num_randomizations': 300})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': True, 'enable_measure': True, 'num_randomizations': 300, 'shots_per_randomization': 100, 'interleave_randomizations': True, 'strategy': 'active'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': True, 'pec_mitigation': False, 'zne': {'noise_factors': [1, 2, 3], 'extrapolator': ['exponential', 'linear', 'fallback'], 'extrapolated_noise_factors': [0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5, 1.6, 1.7000000000000002, 1.8, 1.9000000000000001, 2, 2.1, 2.2, 2.3000000000000003, 2.4000000000000004, 2.5, 2.6, 2.7, 2.8000000000000003, 2.9000000000000004, 3]}}, 'version': 2})
Étape 4 : Post-traiter et retourner le résultat dans le format classique souhaité
Ici, aucune reconstruction n'est nécessaire, comme précédemment ; nous pouvons accéder directement à la valeur d'espérance depuis la sortie de l'exécution pour examiner le résultat.
# AQC results
hw_results = job.result()
hw_results_dicts = [pub_result.data.__dict__ for pub_result in hw_results]
hw_expvals = [
pub_result_data["evs"].tolist() for pub_result_data in hw_results_dicts
]
aqc_expval = hw_expvals[0]
# AQC comparison results
hw_comparison_results = job_comparison.result()
hw_comparison_results_dicts = [
pub_result.data.__dict__ for pub_result in hw_comparison_results
]
hw_comparison_expvals = [
pub_result_data["evs"].tolist()
for pub_result_data in hw_comparison_results_dicts
]
aqc_compare_expval = hw_comparison_expvals[0]
print(f"Exact: \t{reference_expval:.4f}")
print(
f"AQC: \t{aqc_expval:.4f}, |∆| = {np.abs(reference_expval- aqc_expval):.4f}"
)
print(
f"AQC Comparison:\t{aqc_compare_expval:.4f}, |∆| = {np.abs(reference_expval- aqc_compare_expval):.4f}"
)
Exact: -0.5888
AQC: -0.4809, |∆| = 0.1078
AQC Comparison: 1.1764, |∆| = 1.7652
Tracez les résultats des circuits AQC, de comparaison et exact pour le modèle XXZ à 50 sites.
labels = ["AQC Result", "AQC Comparison Result"]
values = [abs(aqc_expval), abs(aqc_compare_expval)]
plt.figure(figsize=(10, 6))
bars = plt.bar(labels, values, color=["tab:blue", "tab:purple"])
plt.axhline(
y=abs(reference_expval), color="red", linestyle="--", label="Exact Result"
)
plt.xlabel("Results")
plt.ylabel("Absolute Expected Value")
plt.title("AQC Result vs AQC Comparison Result (Absolute Values)")
plt.legend()
for bar in bars:
y_val = bar.get_height()
plt.text(
bar.get_x() + bar.get_width() / 2.0,
y_val,
round(y_val, 2),
va="bottom",
)
plt.show()
Conclusion
Ce tutoriel a démontré comment utiliser la Compilation Quantique Approximative avec des réseaux de tenseurs (AQC-Tensor) pour compresser et optimiser des circuits destinés à simuler la dynamique quantique à grande échelle. En utilisant à la fois des modèles de Heisenberg de petite et grande taille, nous avons appliqué AQC-Tensor pour réduire la profondeur de circuit requise pour l'évolution temporelle trotterisée. En générant un ansatz paramétré à partir d'un circuit de Trotter simplifié et en l'optimisant avec des techniques d'état produit matriciel (MPS), nous avons obtenu une approximation à faible profondeur de l'évolution cible qui est à la fois précise et efficace.
Le flux de travail présenté ici met en évidence les principaux avantages d'AQC-Tensor pour la mise à l'échelle des simulations quantiques :
- Compression significative des circuits : AQC-Tensor a réduit la profondeur de circuit nécessaire pour une évolution temporelle complexe, améliorant sa faisabilité sur les dispositifs actuels.
- Optimisation efficace : L'approche MPS a fourni un cadre robuste pour l'optimisation des paramètres, équilibrant fidélité et efficacité computationnelle.
- Exécution prête pour le matériel : La transpilation du circuit optimisé final a garanti qu'il respectait les contraintes du matériel quantique cible.
À mesure que des dispositifs quantiques plus grands et des algorithmes plus avancés émergent, des techniques comme AQC-Tensor deviendront essentielles pour exécuter des simulations quantiques complexes sur du matériel à court terme, démontrant des progrès prometteurs dans la gestion de la profondeur et de la fidélité pour des applications quantiques évolutives.
Enquête sur le tutoriel
Veuillez répondre à cette courte enquête pour nous faire part de vos commentaires sur ce tutoriel. Vos retours nous aideront à améliorer nos contenus et l'expérience utilisateur.