Sari la conținutul principal

Simularea modelului Ising cu kick folosind funcția TEM

Metoda Tensor-network Error Mitigation (TEM) de la Algorithmiq este un algoritm cuantic-clasic hibrid conceput pentru efectuarea atenuării zgomotului în întregime în cadrul etapei clasice de post-procesare. Cu TEM, utilizatorul poate calcula valorile așteptate ale observabilelor, atenuând erorile inevitabile induse de zgomot pe hardware-ul cuantic cu o precizie și eficiență a costurilor mai ridicate, ceea ce o face o opțiune extrem de atractivă atât pentru cercetătorii cuantici, cât și pentru practicienii din industrie.

Acest tutorial demonstrează cum TEM poate obține rezultate semnificative pentru dinamica unui sistem cuantic, care ar fi inaccesibile fără atenuarea erorilor și care necesită substanțial mai multe resurse cuantice dacă sunt utilizate alte metode de atenuare a erorilor, precum PEC și ZNE.

Estimare de utilizare: Acest notebook folosește aproximativ 10 minute QPU pe dispozitivele Heron r3. Timpul de execuție poate depinde substanțial de dispozitivul ales. Estimările de utilizare pe secțiuni pot fi găsite mai jos.

Rularea experimentelor de fizică cu mulți corpi cu atenuarea erorilor folosind funcția TEM

Acest tutorial se bazează pe următoarea referință: L. E. Fischer et al., Nat. Phys. (2026). Această referință discută o simulare reală pe hardware cuantic a până la 91 de qubiți. În acest tutorial, recreăm o simulare similară pe o dimensiune mai mică a circuitului.

Modelul Ising cu kick corespunde modelului Ising obișnuit:

H^I=Jn=0N2Z^nZ^n+1+hn=0N1Z^n\hat{H}_{\text{I}} = J \sum_{n=0}^{N-2} \hat{Z}_n \hat{Z}_{n+1} + h \sum_{n=0}^{N-1} \hat{Z}_n

căruia i se aplică un kick transversal:

H^K=bn=0N1X^n\hat{H}_{K} = b \sum_{n=0}^{N-1} \hat{X}_n

Scopul este de a simula dinamica unei stări sub hamiltonianul Ising cu kick transversal, a cărui evoluție temporală poate fi implementată printr-un operator unitar Floquet U^KI=eiH^KeiH^I\hat{U}_{\text{KI}} = e^{-i \hat{H}_K} e^{-i \hat{H}_I} . Starea inițială de evoluat este cea în care primul qubit se află în starea +|+\rangle, în timp ce ceilalți sunt grupați în perechi și setați în starea Bell (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}.

Cantitatea pe care vrem să o observăm este funcția de corelație. Lucrarea de referință discută cum această cantitate poate fi rescrisă ca un operator Pauli X^\hat{X} pe al nn-lea qubit. După un număr de pași temporali fizici tt, calculăm valoarea operatorului Pauli X^n=t\hat{X}_{n=t}. În funcție de parametrii sistemului, valoarea acestui observabil este egală cu o valoare care poate fi calculată exact sau numai prin metode aproximative. Concret, pentru J=b=π/4|J|=|b|=\pi/4 este egală cu [cos(2h)]t[\cos(2h)]^t, care este valoarea pe care o vom folosi pentru a evalua rezultatele acestui tutorial. Mai mult, la un anumit pas temporal tt, X^nt\langle\hat{X}_{n\neq t}\rangle este zero. Pentru detalii privind obținerea acestor valori și pentru comparație cu rezultatele de simulare clasică aproximativă în afara acestor parametri, consultă L. E. Fischer et al., Nat. Phys. (2026).

TEM funcționează prin caracterizarea inițială a zgomotului pentru fiecare strat unic de porți cu doi qubiți din circuit, precum și caracterizarea erorii de citire. Apoi, circuitul este executat pe mașina cuantică. În final, atenuarea erorilor prin rețea tensorială este efectuată pe resursele clasice IBM Cloud® și este returnată valoarea atenuată. În acest exemplu, circuitul are două straturi unice de caracterizat.

Configurare

Ca cerință preliminară, asigură-te că dependențele necesare sunt instalate.

%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

Atenuarea erorilor cu TEM

Furnizăm aici un circuit care implementează modelul Ising cu kick descris mai sus. Circuitul este pregătit după cum urmează. În primul rând, există o fază de pregătire a stării, în care primul qubit se află în starea +|+\rangle, în timp ce ceilalți sunt în perechi Bell (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}. Aceasta este urmată de structura de tip cărămidă care implementează evoluția unitară U^KI\hat{U}_{\text{KI}}. Numărul de pași temporali fizici corespunde straturilor de circuit t/2t/2. Codul următor descarcă cele două fișiere QASM necesare pentru acest tutorial.

# 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",
)

Putem vizualiza o versiune mică a circuitului, cu 12 qubiți și șase pași temporali:

# 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 of the previous code cell

Apoi, construiește observabilul X^n=t\hat{X}_{n=t}. Acesta este construit ca un șir Pauli simplu cu ordinea corespunzătoare celei utilizate de 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)

În exemplul nostru mic cu 12 qubiți, observabilul arată astfel:

# 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])

Funcțiile Qiskit utilizează PUB-uri ca modalitate de colectare a intrărilor. În cazul nostru, să considerăm un singur circuit și un observabil ca PUB-ul nostru:

# Collect the input PUBs, in this case composed of a
# single circuit and observable
pubs = [(small_circuit, [small_observable])]

Apoi, obținem acces la funcția TEM. Mai întâi configurăm autentificarea necesară la IBM Cloud și selectăm un backend din dispozitivele disponibile. Token-ul, backend-urile disponibile și numele corespunzătoare ale resurselor cloud (CRN) pot fi obținute prin conectarea la contul tău pe panoul 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

Încarcă funcția TEM din Catalogul de funcții Qiskit:

# Load the TEM function from the Qiskit Functions Catalog
catalog = QiskitFunctionsCatalog(
channel=channel,
token=personal_token,
instance=crn,
)
tem = catalog.load("algorithmiq/tem")

Acum putem rula un experiment pe circuitul Ising cu kick cu atenuarea erorilor furnizată de TEM. Folosind setările implicite, TEM poate fi rulat într-un mod simplu cu un timp de execuție QPU estimat de aproximativ 2,5 minute, în funcție de QPU:

tem_job = tem.run(pubs=pubs, backend_name=backend_name)

Cu opțiunile implicite, funcția TEM rulează trei joburi pe computerul cuantic: învățarea zgomotului, atenuarea citirii și eșantionarea circuitului. Numărul de shots utilizat de fiecare dintre acestea poate fi modificat în opțiunile transmise funcției. Implicit, acești parametri sunt setați pentru a obține o precizie de 0,05 în valorile așteptate atenuate. Poți verifica starea job-ului tău pe panoul de bord IBM Quantum Platform sau cu:

print(tem_job.status())
QUEUED

Când starea este DONE, putem verifica rezultatele brute și atenuate. tem_evs definite mai jos sunt valorile așteptate ale observabilelor solicitate, în acest caz doar un singur observabil, X^n=t\langle \hat X_{n=t}\rangle, iar tem_std sunt deviatiile standard corespunzătoare.

# 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

Putem de asemenea verifica câte resurse cuantice au fost utilizate pentru fiecare apel pe IBM Quantum Platform, sau inspectând metadatele rezultatelor din codul 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

Personalizarea parametrilor TEM și a opțiunilor avansate

Funcția TEM oferă mai multe opțiuni avansate pentru personalizarea fluxului de lucru de atenuare a erorilor. Aceste opțiuni îți permit să controlezi precizia, numărul de shots, strategiile de învățare a zgomotului și alți parametri pentru a se potrivi mai bine cerințelor experimentului tău și resurselor cuantice disponibile.

Opțiunile avansate comune sunt:

  • precision: Specifică precizia țintă pentru valorile așteptate atenuate.
  • default_shots: În loc de precision, poți specifica numărul de shots utilizat de job-ul de măsurare.
  • tem_max_bond_dimension: Dimensiunea maximă de legătură utilizată în rețeaua tensorială.
  • tem_compression_cutoff: Valoarea de prag de utilizat pentru rețeaua tensorială.
  • Opțiuni de învățare a zgomotului: Configurează modul în care este caracterizat zgomotul, cum ar fi numărul de repetări sau circuitele de calibrare specifice.
  • private: Asigură că circuitele și rezultatele experimentelor sunt private pentru tine și dezactivează descărcările multiple ale rezultatelor jobului.

Consultă documentația TEM sau Catalogul de funcții Qiskit pentru o listă completă a opțiunilor suportate și descrierile acestora. Poți ajusta acești parametri pentru a echilibra timpul de execuție, utilizarea resurselor și precizia rezultatelor. Poți transmite aceste opțiuni ca un dicționar argumentului options la rularea funcției 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,
}

Opțiunile personalizate pentru cel care învață zgomotul pot fi de asemenea transmise. Acestea urmează definițiile utilizate în Qiskit Runtime 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

Rulează din nou experimentul cu aceste opțiuni personalizate ajustate pentru circuitul nostru. Timpul de execuție estimat este de aproximativ patru minute QPU.

tem_job_custom = tem.run(
pubs=pubs, backend_name=backend_name, options=options
)

Dacă job-ul nu este setat ca privat, putem recupera rezultatul la un moment ulterior. Pentru a face acest lucru, salvează ID-ul jobului afișat aici și folosește 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

Putem acum inspecta rezultatele și metadatele pentru a obține informații despre experiment:

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()

Output of the previous code cell

În final, putem verifica impactul opțiunilor personalizate asupra timpului de execuție QPU și clasic:

# 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

Scalarea TEM la circuite mari

Circuitele mari pot fi, în principiu, rulate cu funcția TEM. Cu toate acestea, este important să fii conștient de limitările resurselor clasice, deoarece TEM este executat pe runnere IBM Cloud cu timpi de execuție potențial foarte lungi. Pentru circuite extrem de mari, contactează echipa de suport TEM la qiskit_ibm@algorithmiq.fi.

Aici rulăm un exemplu cu un circuit mai mare, de 30 de qubiți la scară utilitate, optimizând parametrii TEM pentru viteză în detrimentul preciziei.

# 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])]

Să definim câteva opțiuni orientate spre performanță:

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,
}

În final, rulează experimentul, obține rezultatul și vizualizează-l. Aceasta va dura aproximativ 3,5 minute 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()

Output of the previous code cell