Przejdź do głównej treści

Symulacja uderzonego modelu Isinga z funkcją TEM

Metoda Tensor-network Error Mitigation (TEM) firmy Algorithmiq to hybrydowy algorytm kwantowo-klasyczny zaprojektowany do przeprowadzania mitygacji szumów w całości w fazie klasycznego post-przetwarzania. Dzięki TEM użytkownik może obliczać wartości oczekiwane obserwowalnych, mitygując nieuniknione błędy wywołane szumem na sprzęcie kwantowym z większą dokładnością i efektywnością kosztową, co czyni ją bardzo atrakcyjną opcją zarówno dla badaczy kwantowych, jak i praktyków przemysłowych.

Ten tutorial demonstruje, jak TEM może uzyskać znaczące wyniki dla dynamiki układu kwantowego, które byłyby niedostępne bez mitygacji błędów i które wymagają znacznie więcej zasobów kwantowych przy użyciu innych metod mitygacji błędów, takich jak PEC i ZNE.

Szacunkowe użycie: Ten notatnik zużywa około 10 minut QPU na urządzeniach Heron r3. Czas działania może się znacznie różnić w zależności od wybranego urządzenia. Szacunki użycia dla poszczególnych sekcji można znaleźć poniżej.

Uruchamianie eksperymentów z fizyki wielu ciał z mitygacją błędów przy użyciu funkcji TEM

Ten tutorial jest oparty na następującym odniesieniu: L. E. Fischer et al., Nat. Phys. (2026). To odniesienie omawia rzeczywistą symulację na sprzęcie kwantowym z nawet 91 qubitami. W tym tutorialu odtwarzamy podobną symulację na mniejszym rozmiarze obwodu.

Uderzony model Isinga odpowiada zwykłemu modelowi Isinga:

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

do którego stosowane jest poprzeczne uderzenie:

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

Celem jest symulacja dynamiki stanu pod poprzecznym uderzonym hamiltonianem Isinga, którego ewolucję czasową można zaimplementować przez unitarną Floqueta U^KI=eiH^KeiH^I\hat{U}_{\text{KI}} = e^{-i \hat{H}_K} e^{-i \hat{H}_I} . Stanem początkowym do ewolucji jest stan, w którym pierwszy qubit jest w stanie +|+\rangle, a pozostałe są sparowane i ustawione w stanie Bella (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}.

Wielkością, którą chcemy zaobserwować, jest funkcja korelacji. Artykuł referencyjny omawia, jak ta wielkość może być przepisana jako operator Pauliego X^\hat{X} na nn-tym qubicie. Po określonej liczbie fizycznych kroków czasowych tt obliczamy wartość operatora Pauliego X^n=t\hat{X}_{n=t}. W zależności od parametrów układu wartość tej obserwowalnej jest równa wartości, którą można obliczyć dokładnie lub jedynie za pomocą metod przybliżonych. Konkretnie dla J=b=π/4|J|=|b|=\pi/4 jest równa [cos(2h)]t[\cos(2h)]^t, którą użyjemy jako punkt odniesienia wyników tego tutorialu. Co więcej, przy danym kroku czasowym tt, X^nt\langle\hat{X}_{n\neq t}\rangle wynosi zero. Szczegóły dotyczące uzyskania tych wartości oraz porównanie z przybliżonymi wynikami klasycznej symulacji poza tymi parametrami znajdziesz w L. E. Fischer et al., Nat. Phys. (2026).

TEM działa poprzez najpierw scharakteryzowanie szumu dla każdej unikalnej warstwy bramek dwu-qubitowych w obwodzie, a także scharakteryzowanie błędu odczytu. Następnie obwód jest wykonywany na maszynie kwantowej. Na koniec mitygacja błędów sieci tensorowej jest przeprowadzana na zasobach klasycznych IBM Cloud® i zwracana jest wartość po mitygacji. W tym przykładzie obwód ma dwie unikalne warstwy do scharakteryzowania.

Konfiguracja

Jako warunek wstępny upewnij się, że niezbędne zależności są zainstalowane.

%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

Mitygacja błędów z TEM

Dostarczamy tutaj obwód implementujący uderzony model Isinga opisany powyżej. Obwód jest przygotowany w następujący sposób. Najpierw następuje faza przygotowania stanu, w której pierwszy qubit jest w stanie +|+\rangle, a pozostałe są w parach Bella (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}. Po niej następuje struktura ceglana implementująca ewolucję unitarną U^KI\hat{U}_{\text{KI}}. Liczba fizycznych kroków czasowych odpowiada t/2t/2 warstwom obwodu. Poniższy kod pobiera dwa pliki QASM potrzebne do tego tutorialu.

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

Możemy zwizualizować małą wersję obwodu z 12 qubitami i sześcioma krokami czasowymi:

# 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

Następnie zbuduj obserwowalną X^n=t\hat{X}_{n=t}. Jest ona skonstruowana jako prosty ciąg Pauliego z kolejnością odpowiadającą tej używanej przez 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)

W naszym małym przykładzie 12-qubitowym obserwowalna wygląda tak:

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

Funkcje Qiskit używają PUBów jako sposobu gromadzenia danych wejściowych. W naszym przypadku rozważmy jeden obwód i jedną obserwowalną jako nasz PUB:

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

Następnie uzyskujemy dostęp do funkcji TEM. Najpierw konfigurujemy wymagane uwierzytelnianie w IBM Cloud i wybieramy backend spośród dostępnych urządzeń. Token, dostępne backendy i odpowiadające im nazwy zasobów w chmurze (CRN) można uzyskać, logując się na konto na pulpicie 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

Załaduj funkcję TEM z Katalogu Funkcji Qiskit:

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

Możemy teraz uruchomić eksperyment na obwodzie uderzonego Isinga z mitygacją błędów zapewnioną przez TEM. Używając domyślnych ustawień, TEM można uruchomić w prosty sposób z oczekiwanym czasem działania QPU około 2,5 minuty, w zależności od QPU:

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

Przy domyślnych opcjach funkcja TEM uruchamia trzy zadania na komputerze kwantowym: uczenie szumu, mitygacja odczytu i próbkowanie obwodu. Liczbę shotów używaną przez każde z nich można zmienić w opcjach przekazywanych do funkcji. Domyślnie te parametry są ustawione tak, aby osiągnąć precyzję 0,05 w wartościach oczekiwanych po mitygacji. Możesz sprawdzić status swojego zadania na pulpicie IBM Quantum Platform lub za pomocą:

print(tem_job.status())
QUEUED

Gdy status wynosi DONE, możemy sprawdzić surowe i po mitygacji wyniki. tem_evs zdefiniowane poniżej to wartości oczekiwane żądanych obserwowalnych, w tym przypadku tylko jednej obserwowalnej, X^n=t\langle \hat X_{n=t}\rangle, a tem_std to odpowiadające im odchylenia standardowe.

# 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

Możemy również sprawdzić, ile czasu kwantowego zostało użyte dla każdego wywołania na IBM Quantum Platform, lub sprawdzając metadane wyników z kodu 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

Dostosowywanie parametrów TEM i opcji zaawansowanych

Funkcja TEM zapewnia kilka zaawansowanych opcji do dostosowania przepływu pracy mitygacji błędów. Te opcje pozwalają kontrolować precyzję, liczbę shotów, strategie uczenia szumu i inne parametry, aby lepiej dostosować się do wymagań eksperymentu i dostępnych zasobów kwantowych.

Typowe opcje zaawansowane to:

  • precision: Określ docelową precyzję dla wartości oczekiwanych po mitygacji.
  • default_shots: Zamiast precision możesz określić liczbę shotów używanych przez zadanie pomiarowe.
  • tem_max_bond_dimension: Maksymalny wymiar wiązania używany w sieci tensorowej.
  • tem_compression_cutoff: Wartość progowa używana dla sieci tensorowej.
  • Opcje uczenia szumu: Konfiguruj sposób charakteryzowania szumu, np. liczbę powtórzeń lub określone obwody kalibracyjne.
  • private: Zapewnij, że obwody i wyniki eksperymentów są prywatne i wyłącz wielokrotne pobieranie wyników zadań.

Zapoznaj się z dokumentacją TEM lub Katalogiem Funkcji Qiskit, aby uzyskać pełną listę obsługiwanych opcji i ich opisów. Możesz dostosować te parametry, aby zrównoważyć czas działania, zużycie zasobów i dokładność wyników. Te opcje możesz przekazać jako słownik do argumentu options podczas uruchamiania funkcji 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,
}

Niestandardowe opcje dla uczonego szumu można również przekazać. Podążają one za definicjami używanymi w 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

Uruchom ponownie eksperyment z tymi niestandardowymi opcjami dostosowanymi do naszego obwodu. Oczekiwany czas działania to około czterech minut QPU.

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

Jeśli zadanie nie jest ustawione jako prywatne, możemy odzyskać wynik w późniejszym czasie. Aby to zrobić, zapisz wydrukowany tutaj identyfikator zadania i użyj 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

Możemy teraz sprawdzić wyniki i metadane, aby uzyskać wgląd w eksperyment:

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

Na koniec możemy sprawdzić wpływ niestandardowych opcji na czas działania QPU i klasyczny:

# 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

Skalowanie TEM do dużych obwodów

Duże obwody mogą co do zasady być uruchamiane z funkcją TEM. Jednak ważne jest, aby być świadomym ograniczeń zasobów klasycznych, ponieważ TEM jest wykonywany na zasobach IBM Cloud z potencjalnie bardzo długimi czasami działania. W przypadku ekstremalnie dużych obwodów skontaktuj się z zespołem wsparcia TEM pod adresem qiskit_ibm@algorithmiq.fi.

Tutaj uruchamiamy przykład z większym, obwodem 30-qubitowym w skali użyteczności, optymalizując parametry TEM pod kątem szybkości, a nie dokładności.

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

Zdefiniujmy kilka opcji zorientowanych na wydajność:

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

Na koniec uruchom eksperyment, pobierz wynik i zwizualizuj go. Zajmie to około 3,5 minuty 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