Simuler un modèle d'Ising frappé avec la fonction TEM
La méthode de mitigation d'erreurs par réseau de tenseurs (TEM) d'Algorithmiq est un algorithme hybride quantique-classique conçu pour effectuer la mitigation du bruit entièrement dans l'étape de post-traitement classique. Avec TEM, tu peux calculer les valeurs d'espérance des observables en mitigeant les erreurs inévitables induites par le bruit sur le matériel quantique, avec une précision et une efficacité de coût accrues, ce qui en fait une option très attrayante pour les chercheurs en quantique et les praticiens industriels.
Ce tutoriel montre comment TEM peut obtenir des résultats significatifs pour la dynamique d'un système quantique, qui serait inaccessible sans mitigation d'erreurs et qui nécessite beaucoup plus de ressources quantiques si d'autres méthodes de mitigation d'erreurs telles que PEC et ZNE sont utilisées.
Estimation d'utilisation : ce notebook utilise environ 10 minutes QPU sur les appareils Heron r3. Le temps d'exécution peut varier considérablement selon l'appareil choisi. Des estimations d'utilisation par section se trouvent ci-dessous.
Exécuter des expériences de physique à plusieurs corps avec mitigation d'erreurs via 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 discute d'une simulation réelle sur du matériel quantique allant jusqu'à 91 qubits. Dans ce tutoriel, nous recréons une simulation similaire sur un circuit de plus petite taille.
Le modèle d'Ising frappé correspond au modèle d'Ising habituel :
auquel est appliqué un coup transversal :
L'objectif est de simuler la dynamique d'un état sous le Hamiltonien d'Ising frappé transversal, dont l'évolution temporelle peut être implémentée par un 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 mis dans l'état de Bell .
La quantité que nous voulons observer est la fonction de corrélation. L'article de référence explique comment cette quantité peut être réécrite comme un opérateur de Pauli sur le qubit. Après un certain nombre de pas de temps physiques , nous calculons la valeur de l'opérateur de Pauli . Selon les paramètres du système, la valeur de cet observable est égale à une valeur calculable exactement, ou seulement simulable par des méthodes approchées. Spécifiquement, pour , elle est égale à , ce qui est la valeur que nous utiliserons pour valider les résultats de ce tutoriel. De plus, à un pas de temps donné, est zéro. Pour les détails sur l'obtention de ces valeurs, et pour une comparaison avec des résultats de simulation classique approchée en dehors de ces paramètres, voir L. E. Fischer et al., Nat. Phys. (2026).
TEM fonctionne en caractérisant d'abord le bruit pour chaque couche unique de gates à deux qubits dans le circuit, ainsi que la caractérisation de l'erreur de lecture. Ensuite, le circuit est exécuté sur la machine quantique. Enfin, la mitigation d'erreurs par réseau de tenseurs est effectuée sur les ressources classiques dans IBM Cloud® et la valeur mitigée est retournée. Dans cet exemple, le circuit a deux couches uniques à caractériser.
Configuration
En tant que prérequis, assure-toi que les dépendances nécessaires sont installées.
%pip install numpy matplotlib qiskit qiskit-ibm-catalog qiskit-ibm-runtime pylatexenc qiskit_qasm3_import
import 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 QiskitFunctionsCatalog
Mitigation d'erreurs avec TEM
Nous fournissons ici un circuit qui implémente le modèle d'Ising frappé décrit ci-dessus. Le circuit est préparé comme suit. Premièrement, il y a une phase de préparation d'état, dans laquelle le premier qubit est dans l'état , tandis que les autres sont en paires de Bell . Ceci est suivi par la structure en briques qui implémente l'évolution unitaire . Le nombre de pas de temps physiques correspond à couches de circuit. Le code suivant télécharge les deux fichiers QASM nécessaires pour 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 petite version du circuit, avec 12 qubits et six pas de temps :
# 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)

Ensuite, construis l'observable . Il est construit comme une simple chaîne de Pauli avec l'ordre correspondant à 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 ressemble à ceci :
# Build the observable for the kicked Ising model
small_observable = xt_observable(n_qubits=12, t_steps=6)
print(small_observable)
SparsePauliOp(['IIIIIXIIIIII'],
coeffs=[1.+0.j])
Les fonctions Qiskit utilisent des PUBs comme moyen de collecter les entrées. Dans notre cas, considérons un seul circuit 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 accédons à la fonction TEM. Nous configurons d'abord l'authentification requise pour IBM Cloud et sélectionnons un backend parmi les appareils disponibles. Le token, les backends disponibles et les noms de ressources cloud correspondants (CRN) peuvent être obtenus en se connectant à ton 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 name
Charge la fonction TEM depuis le 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 maintenant exécuter une expérience sur le circuit d'Ising frappé avec la mitigation d'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 attendu d'environ 2,5 minutes, selon le QPU :
tem_job = tem.run(pubs=pubs, backend_name=backend_name)
Avec les options par défaut, la fonction TEM exécute trois jobs sur l'ordinateur quantique : apprentissage du bruit, mitigation de la lecture et échantillonnage du circuit. Le nombre de shots utilisés par chacun de ces jobs peut être modifié dans les options passées à la fonction. Par défaut, ces paramètres sont réglés pour atteindre une précision de 0,05 dans les valeurs d'espérance mitigées. Tu peux vérifier le statut de ton job sur le tableau de bord IBM Quantum Platform ou avec :
print(tem_job.status())
QUEUED
Lorsque le statut est DONE, nous pouvons vérifier les résultats bruts et mitigés. Les tem_evs définis ci-dessous sont les valeurs d'espérance 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}")
TEM Result: 1.031 ± 0.046
Nous pouvons également vérifier la quantité de temps d'exécution quantique utilisée pour chaque appel sur IBM Quantum Platform, ou en inspectant les métadonnées des résultats depuis le 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")
TEM Runtime: 155.0 seconds
Personnaliser les paramètres TEM et options avancées
La fonction TEM propose plusieurs options avancées pour personnaliser ton workflow de mitigation d'erreurs. Ces options te permettent de contrôler la précision, le nombre de shots, les stratégies d'apprentissage du bruit et d'autres paramètres pour mieux répondre aux exigences de ton expérience et aux ressources quantiques disponibles.
Les options avancées courantes sont :
precision: Spécifie la précision cible pour les valeurs d'espérance mitigées.default_shots: Au lieu deprecision, tu peux spécifier le nombre de shots utilisés par le job de mesure.tem_max_bond_dimension: La dimension de liaison maximale utilisée dans le réseau de tenseurs.tem_compression_cutoff: La valeur de coupure à utiliser pour le réseau de tenseurs.- options d'apprentissage du bruit : Configure comment le bruit est caractérisé, comme le nombre de répétitions ou des circuits de calibration spécifiques.
private: Assure que les circuits et les résultats d'expérience sont privés pour toi et désactive les téléchargements multiples des résultats du job.
Consulte la documentation TEM ou le Qiskit Functions Catalog pour une liste complète des options supportées et leurs descriptions. Tu peux ajuster ces paramètres pour équilibrer le temps d'exécution, l'utilisation des ressources et la précision des résultats.
Tu peux passer ces options sous forme de dictionnaire à l'argument options 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,
}
Des options personnalisées pour l'apprentisseur de bruit peuvent également être passées. Elles suivent les définitions utilisées dans le Runtime Qiskit NoiseLearnerOptions :
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_options
Ré-exécute l'expérience avec ces options personnalisées adaptées à notre circuit. Le temps d'exécution attendu est d'environ quatre minutes QPU.
tem_job_custom = tem.run(
pubs=pubs, backend_name=backend_name, options=options
)
Si le job n'est pas défini comme privé, nous pouvons récupérer le résultat ultérieurement. Pour ce faire, sauvegarde l'ID de job imprimé ici et utilise tem_job_custom = catalog.get_job_by_id("your-job-id").
job_id = tem_job_custom.job_id
print(f"Job ID: {job_id}")
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}")
TEM Result: 0.956 ± 0.018
Nous pouvons maintenant inspecter les résultats et les métadonnées pour obtenir un aperçu de 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)
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()
Enfin, nous pouvons vérifier l'impact des options personnalisées sur le temps d'exécution QPU et 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")
QPU Runtime: 342.0 seconds
Classical Runtime: 107.632604 seconds
Passer à l'échelle TEM pour de grands circuits
De grands circuits peuvent, en principe, être exécutés avec la fonction TEM. Cependant, il est important d'être conscient des limitations des ressources classiques, car TEM est exécuté sur des runners IBM Cloud avec des temps d'exécution potentiellement très longs. Pour les circuits extrêmement grands, contacte l'équipe de support TEM à qiskit_ibm@algorithmiq.fi.
Ici, nous exécutons un exemple avec un circuit plus grand à l'échelle utilitaire de 30 qubits, en optimisant les paramètres TEM pour la vitesse plutôt que 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 orientées performance :
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, exécute l'expérience, obtiens le résultat et visualise-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}")
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")
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()