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

Utility-scale experiment I

примітка

Tamiya Onodera (5 July 2024)

Завантажити PDF оригінальної лекції. Зверни увагу, що деякі фрагменти коду можуть застаріти, оскільки це статичні зображення.

Орієнтовний час QPU для запуску цього експерименту — 45 секунд.

1. Вступ до статті про utility

У цьому уроці ми запускаємо схему масштабу utility, яка з'являється в тому, що ми неформально називаємо «статтею про utility», опублікованій у Nature Vol 618, 15 червня 2023 року. Стаття присвячена часовій еволюції двовимірної моделі Ізінга в поперечному полі. Зокрема, розглядається часова динаміка гамільтоніана,

H=HZZ+HX=J(i,j)ZiZj+hiXiH = H_{ZZ} + H_X = - J \sum_{(i,j)} Z_i Z_j + h \sum_{i} X_i

де J>0J > 0 — константа зв'язку між найближчими сусідами зі спінами i<ji < j, а hh — глобальне поперечне поле. Спінова динаміка від початкового стану симулюється за допомогою розкладання Троттера першого порядку оператора часової еволюції,

exp(iHZZδt)=(i,j)exp(iJδtZiZj)=(i,j)RZiZj(2Jδt)exp(iHXδt)=iexp(ihδtXi)=iRXi(2hδt)\begin{aligned} \exp(-i H_{ZZ} \delta t) &= \prod_{(i,j)} \exp (i J \delta t Z_i Z_j) = \prod_{(i,j)} \mathrm{R}_{Z_i Z_j} ( - 2 J \delta t) \\ \exp(-i H_X \delta t) &= \prod_{i} \exp (-i h \delta t X_i ) = \prod_{i} \mathrm{R}_{X_i} ( 2 h \delta t) \end{aligned}

де час еволюції TT дискретизується на T/δtT / \delta t кроків Троттера, а RZiZj(θJ)\mathrm{R}_{Z_i Z_j}(\theta_J) та RXi(θh)\mathrm{R}_{X_i}(\theta_h) — гейти повороту ZZZZ та XX відповідно.

Експерименти проводились на процесорі IBM Quantum® Eagle — 127-кубітному пристрої з топологією heavy-hex, де взаємодії XX застосовувались до всіх кубітів, а взаємодії ZZZZ — до всіх ребер карти зв'язності. Зауважимо, що всі взаємодії ZZZZ не можна застосовувати одночасно через «залежності від даних». Тому карту зв'язності розфарбовують, щоб згрупувати їх у шари. Взаємодії в одному шарі отримують один колір і можуть виконуватись паралельно.

Крім того, для спрощення експерименту зосередились на випадку θJ=π/2\theta_J=-\pi /2.

Ключовий внесок статті полягає в тому, що автори побудували квантові схеми у масштабі, що виходить за межі симуляції вектора стану, запустили їх на шумних квантових комп'ютерах і отримали надійні результати. Тобто вони продемонстрували практичну корисність шумних квантових комп'ютерів. При цьому для пом'якшення помилок шумних пристроїв було застосовано екстраполяцію нульового шуму (ZNE) з імовірнісним підсиленням помилок (PEA).

З того часу такі експерименти і схеми ми називаємо «utility-scale».

1.1 Твоя мета

Твоя мета в цьому уроці — побудувати схему масштабу utility та запустити її на процесорі Eagle. Виводити надійні результати виходить за межі цього ноутбука — частково тому, що PEA на момент написання є експериментальною функцією Qiskit, а частково тому, що застосування ZNE з PEA займає чимало часу.

Конкретно: тобі потрібно побудувати та запустити схему, що відповідає Рисунку 4b статті, і самостійно побудувати «непом'якшені» точки. Як видно, це схема з 127 кубітів ×\times 60 шарів (20 кроків Троттера) зі спостережуваною Z62\langle Z_{62} \rangle. image.png Звучить серйозно?   Не хвилюйся. Останні три уроки цього курсу є підготовчими кроками. Для початку ми продемонструємо менший за масштабом експеримент — побудуємо і запустимо на фейковому пристрої схему з 27 кубітів ×\times 6 шарів (2 кроки Троттера) зі спостережуваною Z13\langle Z_{13} \rangle.

Це все про вступ. Вирушаємо у пригоду масштабу utility!

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime rustworkx
import qiskit

qiskit.__version__
'2.0.2'
#!pip install qiskit_ibm_runtime
#!pip install qiskit_aer
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx

from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import YGate
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import (
QiskitRuntimeService,
fake_provider,
EstimatorV2 as Estimator,
)
from qiskit_aer import AerSimulator
service = QiskitRuntimeService()

2. Підготовка

2.1 Побудова RZZ(-π\pi / 2)

Спочатку зауважимо, що гейт RZZ у загальному випадку потребує двох гейтів CXCX.

from qiskit.circuit.library import RZZGate

θ_h = Parameter("$\\theta_h$")
qc1 = QuantumCircuit(2)
qc1.append(RZZGate(θ_h), [0, 1])
qc1.decompose(reps=1).draw("mpl")

Output of the previous code cell

Як зазначалось вище, у цьому експерименті ми зосереджуємось на гейті RZZ з конкретним кутом, -π\pi / 2. Як показано в статті, його можна реалізувати лише одним гейтом CXCX.

qc2 = QuantumCircuit(2)

qc2.sdg([0, 1])
qc2.append(YGate().power(1 / 2), [1])
qc2.cx(0, 1)
qc2.append(YGate().power(1 / 2).adjoint(), [1])

qc2.draw("mpl")

Output of the previous code cell

Для подальшого використання визначимо гейт на основі цієї схеми.

rzz = qc2.to_gate(label="RZZ")

Спробуємо випадково застосувати щойно визначений rzz.

qc3 = QuantumCircuit(3)
qc3.append(rzz, [0, 1])
qc3.append(rzz, [0, 2])
display(qc3.draw("mpl"))
# display(qc.decompose(reps=1).draw("mpl"))

Output of the previous code cell

Перш ніж рухатись далі, переконаємось у логічній еквівалентності qc1 (гейт RZZ) для -pi/2 та нашого щойно визначеного гейта rzz або qc2:

from qiskit.quantum_info import Operator

op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)

op1.equiv(op2)
True

2.2 Розфарбування карти зв'язності

Розглянемо, як розфарбовувати карту зв'язності бекенда. Це потрібно для групування взаємодій ZZZZ у шари.

Для початку візуалізуємо карту зв'язності бекенда. Зверни увагу, що карти зв'язності всіх поточних пристроїв IBM Quantum мають топологію heavy-hex.

backend = service.least_busy(operational=True, simulator=False)

backend.coupling_map.draw()

Output of the previous code cell

Для розфарбування карти зв'язності використовуємо rustworkx — пакет Python для роботи з графами та складними мережами. Він надає кілька алгоритмів розфарбування, усі вони евристичні й не гарантують мінімального розфарбування.

Оскільки граф heavy-hex є дводольним, обираємо graph_bipartite_edge_color, який має знаходити мінімальне розфарбування для таких графів.

def color_coupling_map(backend):
graph = backend.coupling_map.graph
undirected_graph = graph.to_undirected(multigraph=False)
edge_color_map = rx.graph_bipartite_edge_color(undirected_graph)
if edge_color_map is None:
edge_color_map = rx.graph_greedy_edge_color(undirected_graph)
# build a map from color to a list of edges
edge_index_map = undirected_graph.edge_index_map()
color_edges_map = {color: [] for color in edge_color_map.values()}
for edge_index, color in edge_color_map.items():
color_edges_map[color].append(
(edge_index_map[edge_index][0], edge_index_map[edge_index][1])
)
return edge_color_map, color_edges_map

Графи heavy-hex мають розфарбовуватись трьома кольорами. Перевіримо це для наведеної вище карти зв'язності.

edge_color_map, color_edges_map = color_coupling_map(backend)
print(
f"{backend.name}, {backend.num_qubits}-qubit device, {len(color_edges_map.keys())} colors assigned."
)
ibm_strasbourg, 127-qubit device, 3 colors assigned.

Так і є!

Для наочності розфарбуємо карту зв'язності отриманими кольорами, використовуючи функцію візуалізації rustworkx.

color_str_map = {0: "green", 1: "red", 2: "blue"}

undirected_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
for i in undirected_graph.edge_indices():
undirected_graph.get_edge_data_by_index(i)["color"] = color_str_map[
edge_color_map[i]
]

rx.visualization.graphviz_draw(
undirected_graph, method="neato", edge_attr_fn=lambda edge: {"color": edge["color"]}
)

Output of the previous code cell

3. Розв'язання тротеризованої часової еволюції 2D моделі Ізінга

Визначимо функцію для побудови схеми зі статті для часової еволюції 2D моделі Ізінга. Функція приймає три параметри: бекенд, ціле число, що вказує кількість кроків Троттера, та булевий прапор, що керує вставкою бар'єрів.

def get_utility_circuit(backend, num_steps: int, barrier: bool = False):
num_qubits = backend.num_qubits
_, color_edges_map = color_coupling_map(backend)
θ_h = Parameter("$\\theta_h$")
qc = QuantumCircuit(num_qubits)

for i in range(num_steps):
qc.rx(θ_h, range(num_qubits))

for _, edge_list in color_edges_map.items():
for edge in edge_list:
qc.append(rzz, edge)

if barrier:
qc.barrier()
return qc

Зверни увагу, що ми вже вручну виконали відображення та маршрутизацію кубітів для побудованої схеми. Тому при транспіляції схеми пізніше ми не (не повинні) просити транспілятор робити відображення та маршрутизацію кубітів. Як ти незабаром побачиш, ми викликаємо транспілятор з рівнем оптимізації 1 та методом розміщення "trivial".

Далі визначимо просту функцію для отримання інформації про побудовану схему для швидкої перевірки.

def get_circuit_info(qc: QuantumCircuit, reps: int = 0):
qc0 = qc.decompose(reps=reps)
return (
f"{qc0.num_qubits} qubits × {qc0.depth(lambda x: x.operation.num_qubits == 2)} layers ({qc0.depth()}-depth)"
+ ", "
+ f"""Gate breakdown: {", ".join([f"{k.upper()} {v}" for k, v in qc0.count_ops().items()])}"""
)

Спробуємо ці функції. Ти маєш побачити схему з 27 кубітів ×\times 15 шарів (5 кроків Троттера). Оскільки фейковий пристрій має 28 ребер, має бути 28*5 заплутувальних гейтів.

backend = fake_provider.FakeTorontoV2()
num_steps = 5
qc = get_utility_circuit(backend, num_steps, True)

display(qc.draw(output="mpl", fold=-1))
print(get_circuit_info(qc, reps=0))
print(get_circuit_info(qc, reps=1))

Output of the previous code cell

27 qubits × 15 layers (20-depth),  Gate breakdown: CIRCUIT-165 140, RX 135, BARRIER 5
27 qubits × 15 layers (60-depth), Gate breakdown: SDG 280, UNITARY 280, CX 140, R 135, BARRIER 5

4. Розв'язання задачі на 27 кубітах

Тепер продемонструємо менший за масштабом варіант utility-експерименту. Побудуємо схему з 27 кубітів ×\times 6 шарів (2 кроки Троттера) зі спостережуваною Z13\langle Z_{13} \rangle та запустимо її як на AerSimulator, так і на фейковому пристрої.

Звичайно, ми дотримуємось нашого чотириетапного робочого процесу — «патерну Qiskit», що складається з етапів Map (відображення), Optimize (оптимізація), Execute (виконання) та Post-Process (постобробка). Конкретніше:

  • Відображення класичних вхідних даних на квантові обчислення.
  • Оптимізація схем для квантових обчислень.
  • Виконання схем за допомогою примітивів.
  • Постобробка та повернення результатів у класичному форматі.

Нижче виконується крок Map для створення схеми меншого за масштабом експерименту. Потім один набір кроків Optimize і Execute для AerSimulator і ще один для фейкового пристрою. Нарешті виконується крок Post-Process для побудови графіків результатів.

4.1 Крок 1: Map

backend = fake_provider.FakeTorontoV2()  # a 27 qubit fake device.
num_steps = 2
qc = get_utility_circuit(backend, num_steps)
obs = SparsePauliOp.from_sparse_list(
[("Z", [13], 1)], num_qubits=backend.num_qubits
) # Falcon
angles = [
0,
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
1.0,
np.pi / 2,
] # We try 11 angles for theta_h.

4.2 Кроки 2 і 3: Optimize і Execute (симулятор)

backend_sim = AerSimulator()
transpiled_qc_sim = transpile(
qc, backend_sim, optimization_level=1, layout_method="trivial"
)
transpiled_obs_sim = obs.apply_layout(layout=transpiled_qc_sim.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_sim, reps=1))
27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (16-depth), Gate breakdown: U3 80, CX 56, R 54, U1 32, U 28

Один з користувачів запустив наступну клітинку на MacBook Pro з процесором Intel Core i7 2.3 ГГц (4 ядра) і 32 ГБ оперативної пам'яті 3LPDDR4X під керуванням macOS 14.5. Це зайняло 161 мс реального часу. На різних ноутбуках результати будуть дещо відрізнятись.

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_sim)
pub = (transpiled_qc_sim, transpiled_obs_sim, params)
result_sim = estimator.run([pub]).result()
CPU times: user 231 ms, sys: 186 ms, total: 417 ms
Wall time: 111 ms

4.3 Кроки 2 і 3: Optimize і Execute (фейковий пристрій)

backend_fake = fake_provider.FakeTorontoV2()
transpiled_qc_fake = transpile(
qc, backend_fake, optimization_level=1, layout_method="trivial"
)
transpiled_obs_fake = obs.apply_layout(layout=transpiled_qc_fake.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_fake, reps=1))
27 qubits × 6 layers (23-depth),  Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (49-depth), Gate breakdown: SDG 324, U1 274, H 162, CX 56, U3 14

Коли той самий користувач запустив наступну клітинку в тому ж середовищі, це зайняло 2 хв 19 с реального часу. Виконання схеми на фейковому пристрої задіює шумну симуляцію, яка займає значно більше часу, ніж точна симуляція. Ми рекомендуємо не запускати більші схеми (наприклад, 27 кубітів ×\times 9 шарів з 3 кроками Троттера) на фейковому пристрої.

%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_fake)
pub = (transpiled_qc_fake, transpiled_obs_fake, params)
result_fake = estimator.run([pub]).result()
CPU times: user 4min 42s, sys: 9.35 s, total: 4min 51s
Wall time: 38.3 s

4.4 Крок 4: Post-process

Побудуємо графіки результатів точної та шумної симуляцій. Видно, що шум суттєво впливає на результати FakeToronto.

plt.plot(angles, result_fake[0].data.evs, "o", label="Fake Device")
plt.plot(angles, result_sim[0].data.evs, "o", label="AerSimulator")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{13} \\rangle$")
plt.legend()
plt.show()

Output of the previous code cell

5. Розв'язання задачі на 127 кубітах

Твоя мета — запустити utility-scale-експеримент, про який йшлося на початку. Ти створиш і виконаєш схему з 127 кубітів та 60 шарів (20 кроків Троттера) зі спостережуваною Z62\langle Z_{62} \rangle. Ми рекомендуємо спробувати зробити це самостійно, використовуючи код для версії на 27 кубітів там, де це доречно. Але розв'язання тут також наведено.

Розв'язання:

5.1 Крок 1: Map

# backend_map = service.backend("ibm_brisbane")
backend_map = service.least_busy(operational=True, simulator=False)

num_steps = 20
qc = get_utility_circuit(backend_map, num_steps)
obs = SparsePauliOp.from_sparse_list(
[("Z", [62], 1)], num_qubits=backend_map.num_qubits
) # Eagle
angles = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, np.pi / 2]

5.2 Кроки 2 і 3: Optimize і Execute

Зауважимо, що карта зв'язності процесора Eagle має 144 ребра.

# backend = service.backend("ibm_brisbane")
backend = backend_map

transpiled_qc = transpile(qc, backend, optimization_level=1, layout_method="trivial")
transpiled_obs = obs.apply_layout(layout=transpiled_qc.layout)

print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc))
156 qubits × 60 layers (221-depth),  Gate breakdown: SDG 7040, UNITARY 7040, CX 3520, R 3120
156 qubits × 60 layers (201-depth), Gate breakdown: RZ 11933, SX 6240, CZ 3520
params = [[p] for p in angles]
estimator = Estimator(mode=backend)
pub = (transpiled_qc, transpiled_obs, params)
job = estimator.run([pub])

job_id = job.job_id()
print(f"job id={job_id}")
job id=d1479n6qf56g0081sxa0

5.3 Post-process

Наводимо значення для «пом'якшених» точок на Рисунку 4b статті про utility. Побудуй їх разом зі своїми результатами.

result_paper = [
1.0171,
1.0044,
0.9563,
0.9602,
0.8394,
0.8120,
0.5466,
0.4556,
0.1953,
0.0141,
0.0117,
]

# REPLACE WITH YOUR OWN JOB ID
job = service.job(job_id)

plt.plot(angles, job.result()[0].data.evs, "o", label=f"{job.backend().name}")
plt.plot(angles, result_paper, "o", label="Utility Paper")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{62} \\rangle$")
plt.legend()
plt.show()

Output of the previous code cell

Чи схожі твої результати на «непом'якшені» точки Рисунку 4b?   Вони можуть дуже відрізнятись залежно від пристрою та його стану на момент проведення експерименту. Не хвилюйся про самі результати. Ми перевіримо, чи правильно ти написав код. Якщо так — вітаємо, ти вийшов на стартову лінію ери utility.

Як і у статті про utility, науковці по всьому світу докладають неабиякої винахідливості, щоб витягти значущі результати навіть в умовах шуму. Кінцева мета цих колективних зусиль — квантова перевага: стан, за якого квантові комп'ютери здатні розв'язувати деякі практично важливі задачі швидше, з більшою точністю або дешевше, ніж класичні комп'ютери. Це, мабуть, буде не одна подія, а ціла ера, в якій класичне відтворення квантових результатів займатиме дедалі більше часу, аж поки ця перевага квантових комп'ютерів не стане критично важливою. Одне є зрозумілим щодо квантової переваги: ми досягнемо її лише через utility-scale-експерименти. Якщо цей курс надихне тебе долучитись до цього пошуку, сповненого викликів і захоплення, ми будемо дуже раді.

Посилання