Simuler un modèle d'Ising avec la fonction TEM
La méthode TEM (Tensor-network Error Mitigation) d'Algorithmiq est un algorithme hybride quantique-classique conçu pour atténuer le bruit entièrement au cours de la phase de post-traitement classique. Avec TEM, l'utilisateur peut calculer les valeurs attendues des observables, atténuant ainsi les erreurs inévitables induites par le bruit qui se produisent sur le matériel quantique avec une précision et une rentabilité accrues, ce qui en fait une option très intéressante pour les chercheurs quantiques et les professionnels du secteur.
Ce tutoriel montre comment la TEM peut obtenir des résultats significatifs pour la dynamique d'un système quantique, qui seraient inaccessibles sans atténuation des erreurs et qui nécessiteraient beaucoup plus de ressources quantiques si d'autres méthodes d'atténuation des erreurs telles que la PEC et la ZNE étaient utilisées.
Estimation d'utilisation : ce notebook utilise environ 10 minutes QPU sur les appareils Heron r3. La durée d'exécution peut dépendre considérablement de l'appareil choisi. Les estimations d'utilisation par section sont disponibles ci-dessous.
Réalisez des expériences de physique à plusieurs corps avec atténuation des erreurs grâce à la fonction TEM
Ce tutoriel est basé sur la référence suivante : L. E. Fischer et al., Nat. Phys. (2026). Cette référence traite d'une simulation réelle sur du matériel quantique pouvant atteindre 91 qubits. Dans ce tutoriel, nous recréons une simulation similaire sur un circuit de taille réduite.
Le modèle d'Ising modifié correspond au modèle d'Ising habituel :
auquel est appliqué un coup de pied transversal :
L'objectif est de simuler la dynamique d'un état sous l'hamiltonien d'Ising à coup transversal, dont l'évolution temporelle peut être mise en œuvre par une unitaire de Floquet. L'état initial à faire évoluer est celui dans lequel le premier qubit est dans l'état , tandis que les autres sont appariés et placés dans l'état de Bell .
La quantité que nous voulons observer est la fonction de corrélation. Le document de référence explique comment cette quantité peut être réécrite sous la forme d'un opérateur de Pauli d' e sur le qubit d' . Après un certain nombre d'étapes physiques , nous calculons la valeur de l'opérateur de Pauli . En fonction des paramètres du système, la valeur de cette observable est égale à une valeur qui peut être calculée avec exactitude ou seulement simulée à l'aide de méthodes approximatives. Plus précisément, pour , il est égal à , qui est la valeur que nous utiliserons pour comparer les résultats de ce tutoriel. De plus, à un instant donné , est égal à zéro. Pour plus de détails sur l'obtention de ces valeurs et pour une comparaison avec les résultats approximatifs de la simulation classique en dehors de ces paramètres, voir L. E. Fischer et al., Nat. Phys. (2026).
La TEM fonctionne en caractérisant d'abord le bruit pour chaque couche unique de portes à deux qubits dans le circuit, ainsi qu'en caractérisant l'erreur de lecture. Ensuite, le circuit est exécuté sur la machine quantique. Enfin, l'atténuation des erreurs du réseau tensoriel est effectuée sur les ressources classiques dans IBM Cloud® et la valeur atténuée est renvoyée. Dans cet exemple, le circuit comporte deux couches distinctes à caractériser.
Configuration
Comme condition préalable, assurez-vous que les dépendances nécessaires sont installées.
%pip install numpy matplotlib qiskit qiskit-ibm-catalog qiskit-ibm-runtime pylatexenc qiskit_qasm3_importimport os
from matplotlib import pyplot as plt
import numpy as np
from qiskit.quantum_info import SparsePauliOp
from qiskit.qasm3 import load
from qiskit_ibm_catalog import QiskitFunctionsCatalogAtténuation des erreurs avec TEM
Nous fournissons ici un circuit qui met en œuvre le modèle d'Ising avec coup de pied décrit ci-dessus. Le circuit est préparé comme suit. Tout d'abord, il y a une phase de préparation de l'état, dans laquelle le premier qubit est dans l'état , tandis que les autres sont dans des paires de Bell . Vient ensuite la structure en briques qui met en œuvre l'évolution unitaire . Le nombre d'étapes physiques correspond aux couches du circuit .
Le code suivant télécharge les deux fichiers QASM nécessaires à ce tutoriel.
# Download required QASM files
import urllib
urllib.request.urlretrieve(
"https://ibm.box.com/shared/static/swy5jtq309b0xpzluzlmsmj908yphes8.qasm",
"ki_30q.qasm",
)
urllib.request.urlretrieve(
"https://ibm.box.com/shared/static/et3gkodonw6gsp2trs43lzaozrdtiu7s.qasm",
"ki_12q.qasm",
)Nous pouvons visualiser une version réduite du circuit, avec 12 qubits et six étapes temporelles :
# Parameters of the kicked Ising model
h = 0.0
num_qubits = 12
t_steps = 6
# Load the circuit for the kicked Ising model
small_circuit = load("ki_12q.qasm")
# Draw the circuit
small_circuit.draw("mpl", scale=0.25, fold=-1)Output:
Ensuite, construisez l'observable, . Il est construit comme une simple chaîne de Pauli dont l'ordre correspond à celui utilisé par Qiskit :
def xt_observable(n_qubits, t_steps):
pauli_str = "".join(["I" * t_steps, "X", "I" * (n_qubits - t_steps - 1)])
pauli_str = pauli_str[::-1] # Reverse the string to match qiskit order
return SparsePauliOp(data=pauli_str, coeffs=1.0)Dans notre petit exemple à 12 qubits, l'observable se présente comme suit :
# Build the observable for the kicked Ising model
small_observable = xt_observable(n_qubits=12, t_steps=6)
print(small_observable)Output:
SparsePauliOp(['IIIIIXIIIIII'],
coeffs=[1.+0.j])
Qiskit Functions utiliser les PUB comme moyen de collecter les contributions. Dans notre cas, considérons un circuit unique et observable comme notre PUB :
# Collect the input PUBs, in this case composed of a
# single circuit and observable
pubs = [(small_circuit, [small_observable])]Ensuite, nous avons accès à la fonction TEM. Nous configurons d'abord l'authentification requise pour IBM Cloud et sélectionnons un backend parmi les appareils disponibles. Le jeton, les backends disponibles et les noms de ressources cloud (CRN) correspondants peuvent être obtenus en vous connectant à votre compte sur le tableau de bord IBM Quantum Platform.
# Set IBM Quantum credentials and backend configuration
personal_token = os.environ.get(
"QISKIT_IBM_TOKEN", "<API-KEY>"
) # Replace with your personal token or set the environment variable
channel = "ibm_quantum_platform"
crn = "your_crn" # Replace with the Cloud Resource Name (CRN)
# Select the QPU backend
backend_name = "ibm_qpu_name" # Replace with your desired backend's nameChargez la fonction TEM à partir de l' Qiskit Functions Catalog :
# Load the TEM function from the Qiskit Functions Catalog
catalog = QiskitFunctionsCatalog(
channel=channel,
token=personal_token,
instance=crn,
)
tem = catalog.load("algorithmiq/tem")Nous pouvons désormais mener une expérience sur le circuit d'Ising activé avec l'atténuation des erreurs fournie par TEM. Avec les paramètres par défaut, TEM peut être exécuté de manière simple avec un temps d'exécution QPU prévu d'environ 2.5 minutes, en fonction du QPU :
tem_job = tem.run(pubs=pubs, backend_name=backend_name)Avec les options par défaut, la fonction TEM exécute trois tâches sur l'ordinateur quantique : apprentissage du bruit, atténuation de la lecture et échantillonnage des circuits. Le nombre de clichés utilisés par chacun d'entre eux peut être modifié dans les options transmises à la fonction. Par défaut, ces paramètres sont définis pour obtenir une précision de l'ordre de l' 0.05 e dans les valeurs d'espérance atténuées.
Vous pouvez vérifier l'état d'avancement de votre tâche sur le tableau de bord IBM Quantum Platform ou à l'aide de :
print(tem_job.status())Output:
QUEUED
Lorsque le statut est DONE, nous pouvons vérifier les résultats bruts et atténués. Les tem_evs définis ci-dessous sont les valeurs attendues des observables demandés, dans ce cas un seul observable, , et tem_std sont les écarts types correspondants.
# Get the results of the TEM job
tem_results = tem_job.result()[
0
] # Get the first and only result from the job
tem_evs = tem_results.data.evs[0]
tem_std = tem_results.data.stds[0]
print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")Output:
TEM Result: 1.031 ± 0.046
Nous pouvons également vérifier combien de temps d'exécution quantique a été utilisé pour chaque appel sur IBM Quantum Platform, ou en inspectant les métadonnées de résultat du code Python.
# Get the TEM job runtime
tem_runtime = tem_job.result().metadata["resource_usage"][
"RUNNING: EXECUTING_QPU"
]["QPU_TIME"]
print(f"TEM Runtime: {tem_runtime} seconds")Output:
TEM Runtime: 155.0 seconds
Personnalisez les paramètres TEM et les options avancées
La fonction TEM offre plusieurs options avancées pour personnaliser votre flux de travail d'atténuation des erreurs. Ces options vous permettent de contrôler la précision, le nombre de tirs, les stratégies d'apprentissage du bruit et d'autres paramètres afin de mieux répondre aux exigences de votre expérience et aux ressources quantiques disponibles.
Les options avancées courantes sont les suivantes :
precision: Spécifiez la précision cible pour les valeurs d'espérance atténuées.default_shots: Au lieu deprecision, vous pouvez spécifier le nombre de prises utilisées par la tâche de mesure.tem_max_bond_dimension: Dimension maximale de liaison utilisée dans le réseau tensoriel.tem_compression_cutoff: Valeur seuil à utiliser pour le réseau de tenseurs.- Options d'apprentissage du bruit : configurez la manière dont le bruit est caractérisé, par exemple le nombre de répétitions ou les circuits d'étalonnage spécifiques.
private: Assurez-vous que les circuits et les résultats des expériences restent privés et désactivez les téléchargements multiples des résultats des tâches.
Reportez-vous à la documentation TEM ou à l' Qiskit Functions Catalog pour obtenir la liste complète des options prises en charge et leurs descriptions. Vous pouvez ajuster ces paramètres pour trouver le bon équilibre entre la durée d'exécution, l'utilisation des ressources et la précision des résultats.
Vous pouvez passer ces options sous forme de dictionnaire à options l'argument lors de l'exécution de la fonction TEM :
options = {
"default_shots": 10_000,
"tem_max_bond_dimension": 512,
"tem_compression_cutoff": 1e-16,
# This option helps optimizing the measurement
# stage since the observable is strongly biased
# toward the X operator for all the qubits.
"compute_shadows_bias_from_observable": True,
# set to True to keep experiment results private,
# recommended for confidential circuits
"private": False,
}Il est également possible de passer des options personnalisées pour l'algorithme d'apprentissage du bruit. Elles reprennent les définitions utilisées dans le : qiskit-ibm-runtimeNoiseLearnerOptions
nl_options = {
"num_randomizations": 32,
"max_layers_to_learn": 2,
"shots_per_randomization": 128,
"layer_pair_depths": [0, 1, 2, 4, 16, 32],
}
# add noise learning options to the overall options
options |= nl_optionsRépétez l'expérience avec ces options personnalisées adaptées à notre circuit. La durée d'exécution prévue est d'environ quatre minutes QPU.
tem_job_custom = tem.run(
pubs=pubs, backend_name=backend_name, options=options
)Si la tâche n'est pas définie comme privée, nous pouvons récupérer le résultat ultérieurement. Pour ce faire, enregistrez l'ID de tâche imprimé ici et utilisez tem_job_custom = catalog.get_job_by_id("your-job-id").
job_id = tem_job_custom.job_id
print(f"Job ID: {job_id}")Output:
Job ID: 1ba10094-a541-457a-9287-dbd49306d12d
results_custom = tem_job_custom.result()
tem_evs = results_custom[0].data.evs[0]
tem_std = results_custom[0].data.stds[0]
print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")Output:
TEM Result: 0.956 ± 0.018
Nous pouvons désormais examiner les résultats et les métadonnées pour mieux comprendre l'expérience :
metadata_custom = results_custom[0].metadata
unmitigated_evs = metadata_custom["evs_non_mitigated"][0]
unmitigated_stds = metadata_custom["stds_non_mitigated"][0]
print(f"Unmitigated Result: {unmitigated_evs:.3f} ± {unmitigated_stds:.3f}")
# Exact result for the kicked Ising model from the reference paper
exact_evs = np.cos(2 * h) ** t_steps
print("Exact Result:", exact_evs)Output:
Unmitigated Result: 0.894 ± 0.015
Exact Result: 1.0
# Plot comparing the different expectation values
plt.bar(
["Unmitigated", "TEM"],
[unmitigated_evs, tem_evs],
yerr=[unmitigated_stds, tem_std],
color=["grey", "c"],
)
plt.hlines(y=exact_evs, xmin=-0.5, xmax=1.5, colors="r", linestyles="dashed")
plt.ylabel("Expectation Value")
plt.ylim(0, 1.1)
plt.show()Output:
Enfin, nous pouvons vérifier l'impact des options personnalisées sur le QPU et le temps d'exécution classique :
# Get the metadata of the TEM job
job_metadata = results_custom.metadata
# Get the runtime of the TEM job
qpu_runtime = job_metadata["resource_usage"]["RUNNING: EXECUTING_QPU"][
"QPU_TIME"
]
classical_runtime = (
job_metadata["resource_usage"]["RUNNING: OPTIMIZING_FOR_HARDWARE"][
"CPU_TIME"
]
+ job_metadata["resource_usage"]["RUNNING: POST_PROCESSING"]["CPU_TIME"]
)
print(f"QPU Runtime: {qpu_runtime} seconds")
print(f"Classical Runtime: {classical_runtime} seconds")Output:
QPU Runtime: 342.0 seconds
Classical Runtime: 107.632604 seconds
Adapter le TEM aux grands circuits
Les grands circuits peuvent, en principe, être exécutés avec la fonction TEM. Cependant, il est important d'être conscient des limites des ressources classiques, car TEM est exécuté sur des runners d' IBM Cloud s dont les temps d'exécution peuvent être très longs. Pour les circuits extrêmement volumineux, contactez l'équipe d'assistance TEM à l'adresse qiskit\ [email protected].
Nous présentons ici un exemple avec un circuit plus grand, de taille industrielle, à 30 qubits, en optimisant les paramètres TEM pour la vitesse plutôt que pour la précision.
# Kicked Ising model parameters
n_qubits = 30
t_steps = 15
h = 0.0
# Load the circuit for the kicked Ising model
circuit = load("ki_30q.qasm")
# Build the observable for the kicked Ising model
observable = xt_observable(n_qubits=n_qubits, t_steps=t_steps)
# Collect the input PUBs, in this case composed of a
# single circuit and observable
pubs = [(circuit, [observable])]Définissons quelques options axées sur les performances :
options = {
"num_randomizations": 32,
"max_layers_to_learn": 2,
"shots_per_randomization": 128,
"layer_pair_depths": [0, 1, 2, 4, 16, 32, 64],
"default_shots": 5_000,
"tem_max_bond_dimension": 128,
"tem_compression_cutoff": 1e-10,
"compute_shadows_bias_from_observable": True,
"private": False,
}Enfin, lancez l'expérience, obtenez le résultat et visualisez-le. Cela prendra environ 3.5 minutes QPU.
tem_job_large = tem.run(pubs=pubs, backend_name=backend_name, options=options)job_id = tem_job_large.job_id
print(f"Job ID: {job_id}")Output:
Job ID: 9f3f190f-f4b0-4dcb-bb83-5f71f37d0d77
results_large = tem_job_large.result()
tem_evs = results_large[0].data.evs[0]
tem_std = results_large[0].data.stds[0]
print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")
# Get the metadata of the TEM job
job_metadata = tem_job_large.result().metadata
# Get the runtime of the TEM job
qpu_runtime = job_metadata["resource_usage"]["RUNNING: EXECUTING_QPU"][
"QPU_TIME"
]
classical_runtime = (
job_metadata["resource_usage"]["RUNNING: OPTIMIZING_FOR_HARDWARE"][
"CPU_TIME"
]
+ job_metadata["resource_usage"]["RUNNING: POST_PROCESSING"]["CPU_TIME"]
)
print(f"QPU Runtime: {qpu_runtime} seconds")
print(f"Classical Runtime: {classical_runtime} seconds")Output:
TEM Result: 0.794 ± 0.026
QPU Runtime: 203.0 seconds
Classical Runtime: 251.71805499999996 seconds
# Plot comparing the different expectation values
metadata_large = results_large[0].metadata
unmitigated_evs = metadata_large["evs_non_mitigated"][0]
unmitigated_stds = metadata_large["stds_non_mitigated"][0]
exact_evs = np.cos(2 * h) ** t_steps
plt.bar(
["Unmitigated", "TEM"],
[unmitigated_evs, tem_evs],
yerr=[unmitigated_stds, tem_std],
color=["grey", "c"],
)
plt.hlines(y=exact_evs, xmin=-0.5, xmax=1.5, colors="r", linestyles="dashed")
plt.ylabel("Expectation Value")
plt.ylim(0, 1.1)
plt.show()Output: