Перейти до основного вмісту

Симуляція моделі Ізінга з поштовхом за допомогою функції TEM

Метод Tensor-network Error Mitigation (TEM) від Algorithmiq — це гібридний квантово-класичний алгоритм, розроблений для виконання пом'якшення шуму повністю в рамках класичного етапу постобробки. За допомогою TEM користувач може обчислювати середні значення спостережуваних, пом'якшуючи неминучі помилки, спричинені шумом на квантовому залізі, з більшою точністю та рентабельністю, що робить його надзвичайно привабливим варіантом як для квантових дослідників, так і для промислових практиків.

Цей посібник демонструє, як TEM може отримати значущі результати для динаміки квантової системи, які були б недоступні без пом'якшення помилок і які вимагають значно більше квантових ресурсів при використанні інших методів пом'якшення помилок, таких як PEC та ZNE.

Оцінка використання: Цей блокнот використовує приблизно 10 хвилин QPU на пристроях Heron r3. Час виконання може суттєво залежати від обраного пристрою. Оцінки використання для кожного розділу наведено нижче.

Запуск експериментів з фізики багатьох тіл з пом'якшенням помилок за допомогою функції TEM

Цей посібник базується на такій роботі: L. E. Fischer et al., Nat. Phys. (2026). У цій роботі обговорюється реальна симуляція на квантовому залізі до 91 кубіта. У цьому посібнику ми відтворюємо аналогічну симуляцію на менших розмірах кола.

Модель Ізінга з поштовхом відповідає звичайній моделі Ізінга:

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

до якої застосовується поперечний поштовх:

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

Мета — симулювати динаміку стану під поперечним гамільтоніаном Ізінга з поштовхом, еволюція якого може бути реалізована унітарним оператором Флоке U^KI=eiH^KeiH^I\hat{U}_{\text{KI}} = e^{-i \hat{H}_K} e^{-i \hat{H}_I} . Початковим станом для еволюції є той, у якому перший кубіт знаходиться в стані +|+\rangle, а інші попарно об'єднані і встановлені у стан Белла (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}.

Величина, яку ми хочемо спостерігати, — це кореляційна функція. Довідкова стаття обговорює, як цю величину можна перезаписати як оператор Паулі X^\hat{X} на nn-му кубіті. Після певної кількості фізичних часових кроків tt ми обчислюємо значення оператора Паулі X^n=t\hat{X}_{n=t}. Залежно від параметрів системи значення цього спостережуваного дорівнює значенню, яке можна обчислити точно або лише наближеними методами. Зокрема, для J=b=π/4|J|=|b|=\pi/4 воно дорівнює [cos(2h)]t[\cos(2h)]^t, яке ми будемо використовувати для оцінки результатів цього посібника. Крім того, при заданому часовому кроці tt X^nt\langle\hat{X}_{n\neq t}\rangle дорівнює нулю. Для деталей щодо отримання цих значень та порівняння з наближеними результатами класичної симуляції поза цими параметрами дивись L. E. Fischer et al., Nat. Phys. (2026).

TEM працює шляхом спочатку характеризації шуму для кожного унікального шару двокубітних вентилів у колі, а також характеризації помилки зчитування. Потім коло виконується на квантовій машині. Нарешті, пом'якшення помилок тензорної мережі виконується на класичних ресурсах IBM Cloud® і повертається пом'якшене значення. У цьому прикладі коло має два унікальні шари для характеризації.

Налаштування

Як передумову переконайся, що необхідні залежності встановлено.

%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

Пом'якшення помилок за допомогою TEM

Тут ми надаємо коло, що реалізує модель Ізінга з поштовхом, описану вище. Коло підготовлено наступним чином. По-перше, є фаза підготовки стану, в якій перший кубіт знаходиться в стані +|+\rangle, а інші — в парах Белла (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}. Після неї йде цегляна структура, що реалізує унітарну еволюцію U^KI\hat{U}_{\text{KI}}. Кількість фізичних часових кроків відповідає шарам кола t/2t/2. Наступний код завантажує два QASM-файли, необхідних для цього посібника.

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

Ми можемо візуалізувати невелику версію кола з 12 кубітами та шістьма часовими кроками:

# 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

Далі побудуй спостережуваний X^n=t\hat{X}_{n=t}. Він побудований як простий рядок Паулі з порядком, що відповідає тому, що використовується 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)

У нашому маленькому прикладі з 12 кубітами спостережуваний виглядає так:

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

Функції Qiskit використовують PUB-и як спосіб збору вхідних даних. У нашому випадку розглянемо одне коло та один спостережуваний як наш PUB:

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

Далі ми отримуємо доступ до функції TEM. Спочатку ми налаштовуємо необхідну автентифікацію в IBM Cloud та вибираємо бекенд із доступних пристроїв. Токен, доступні бекенди та відповідні імена хмарних ресурсів (CRN) можна отримати, увійшовши в обліковий запис на панелі керування 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

Завантаж функцію TEM з Каталогу функцій Qiskit:

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

Тепер ми можемо запустити експеримент на колі Ізінга з поштовхом із пом'якшенням помилок, наданим TEM. Використовуючи налаштування за замовчуванням, TEM можна запустити простим способом з очікуваним часом виконання QPU близько 2,5 хвилини залежно від QPU:

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

При налаштуваннях за замовчуванням функція TEM запускає три завдання на квантовому комп'ютері: навчання шуму, пом'якшення зчитування та вибірка кола. Кількість shots, що використовується кожним із них, можна змінити в параметрах, що передаються функції. За замовчуванням ці параметри встановлені для досягнення точності 0,05 у пом'якшених середніх значеннях. Ти можеш перевірити статус свого завдання на панелі керування IBM Quantum Platform або за допомогою:

print(tem_job.status())
QUEUED

Коли статус DONE, ми можемо перевірити необроблені та пом'якшені результати. tem_evs, визначені нижче, — це середні значення запитуваних спостережуваних, у цьому випадку лише одного спостережуваного, X^n=t\langle \hat X_{n=t}\rangle, а tem_std — відповідні стандартні відхилення.

# 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

Ми також можемо перевірити, скільки квантового часу виконання було використано для кожного виклику на IBM Quantum Platform, або перевіривши метадані результатів з коду 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

Налаштування параметрів TEM та розширених параметрів

Функція TEM надає кілька розширених параметрів для налаштування робочого процесу пом'якшення помилок. Ці параметри дозволяють тобі контролювати точність, кількість shots, стратегії навчання шуму та інші параметри для кращої відповідності вимогам твого експерименту та доступним квантовим ресурсам.

Типові розширені параметри:

  • precision: Вкажи цільову точність для пом'якшених середніх значень.
  • default_shots: Замість precision можна вказати кількість shots, що використовується завданням вимірювання.
  • tem_max_bond_dimension: Максимальний розмір зв'язку, що використовується в тензорній мережі.
  • tem_compression_cutoff: Порогове значення для тензорної мережі.
  • Параметри навчання шуму: Налаштуй спосіб характеризації шуму, наприклад кількість повторень або конкретні калібраційні кола.
  • private: Забезпеч конфіденційність кіл та результатів експериментів і вимкни багаторазове завантаження результатів завдання.

Зверни до документації TEM або Каталогу функцій Qiskit, щоб отримати повний список підтримуваних параметрів та їхніх описів. Ти можеш налаштувати ці параметри, щоб збалансувати час виконання, використання ресурсів та точність результатів. Ти можеш передати ці параметри як словник аргументу options при запуску функції 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,
}

Також можна передати власні параметри для навчання шуму. Вони відповідають визначенням, що використовуються в 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

Запусти експеримент знову з цими власними параметрами, налаштованими для нашого кола. Очікуваний час виконання — приблизно чотири хвилини QPU.

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

Якщо завдання не встановлено як приватне, ми можемо отримати результат пізніше. Для цього збережи ID завдання, надрукований тут, і використовуй 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

Тепер ми можемо перевірити результати та метадані, щоб отримати уявлення про експеримент:

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

Нарешті, ми можемо перевірити вплив власних параметрів на час виконання QPU та класичного процесора:

# 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

Масштабування TEM до великих кіл

Великі кола можуть принципово запускатися за допомогою функції TEM. Однак важливо усвідомлювати обмеження класичних ресурсів, оскільки TEM виконується на раннерах IBM Cloud з потенційно дуже тривалим часом виконання. Для надзвичайно великих кіл зв'яжись з командою підтримки TEM за адресою qiskit_ibm@algorithmiq.fi.

Тут ми запускаємо приклад із більшим колом на 30 кубітів у корисному масштабі, оптимізуючи параметри TEM для швидкості, а не точності.

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

Визначимо кілька параметрів, орієнтованих на продуктивність:

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

Нарешті, запусти експеримент, отримай результат та візуалізуй його. Це займе приблизно 3,5 хвилини 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