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

Об'єднуємо все разом з Qiskit Runtime

Підсумок

Вікторія Ліпінська підбиває підсумок того, що ми вивчили досі.

Посилання

Статті, на які посилається наведене відео:

VQE за допомогою шаблонів Qiskit

Ми маємо всі необхідні компоненти для обчислення VQE:

  • Гамільтоніан
  • Анзац
  • Класичний оптимізатор

Тепер залишається лише поєднати їх у рамках фреймворку шаблонів Qiskit.

Крок 1: Відображення класичних вхідних даних на квантову задачу

Як зазначалося раніше, тут ми припускаємо, що відповідно відформатований гамільтоніан вже було отримано. Якщо є питання щодо цього, звернись до уроку про гамільтоніани. Блок коду нижче налаштовує компоненти, пояснені в попередніх уроках. Тут ми обрали H2 для моделювання, оскільки його гамільтоніан достатньо компактний, щоб записати повністю.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime scipy
# General imports
import numpy as np
from qiskit.quantum_info import SparsePauliOp

# Hamiltonian obtained from a previous lesson

H = SparsePauliOp(
[
"IIII",
"IIIZ",
"IZII",
"IIZI",
"ZIII",
"IZIZ",
"IIZZ",
"ZIIZ",
"IZZI",
"ZZII",
"ZIZI",
"YYYY",
"XXYY",
"YYXX",
"XXXX",
],
coeffs=[
-0.09820182 + 0.0j,
-0.1740751 + 0.0j,
-0.1740751 + 0.0j,
0.2242933 + 0.0j,
0.2242933 + 0.0j,
0.16891402 + 0.0j,
0.1210099 + 0.0j,
0.16631441 + 0.0j,
0.16631441 + 0.0j,
0.1210099 + 0.0j,
0.17504456 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
],
)

nuclear_repulsion = 0.7199689944489797

Обираємо схему efficient_su2 і оптимізатор COBYLA для початку.

# Pre-defined ansatz circuit
from qiskit.circuit.library import efficient_su2

# SciPy minimizer routine
from scipy.optimize import minimize

# Plotting functions

# Random initial state and efficient_su2 ansatz
ansatz = efficient_su2(H.num_qubits, su2_gates=["rx"], entanglement="linear", reps=1)
x0 = 2 * np.pi * np.random.random(ansatz.num_parameters)
print(ansatz.decompose().depth())
ansatz.decompose().draw("mpl")
5

Output of the previous code cell

Тепер будуємо нашу функцію вартості. Вона пов'язана з гамільтоніаном, але відрізняється від нього тим, що гамільтоніан — це оператор, а нам потрібна функція, яка повертає значення математичного сподівання цього оператора, використовуючи Estimator. Звичайно, для цього вона використовує анзац і варіаційні параметри, тому всі вони є аргументами. Нижче визначаємо дещо різні версії для реального заліза та симуляторів.

def cost_func(params, ansatz, H, estimator):
pub = (ansatz, [H], [params])
result = estimator.run(pubs=[pub]).result()
energy = result[0].data.evs[0]
return energy

# def cost_func_sim(params, ansatz, H, estimator):
# energy = estimator.run(ansatz, H, parameter_values=params).result().values[0]
# return energy

Крок 2: Оптимізація задачі для квантового виконання.

Хочемо, щоб наш код виконувався якомога ефективніше на обраному залізі. Тому необхідно вибрати бекенд для початку кроку оптимізації. Код нижче обирає найменш завантажений бекенд, доступний для тебе.

# To run on hardware, select the backend with the fewest number of jobs in the queue
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService(channel="ibm_quantum_platform")
backend = service.least_busy(operational=True, simulator=False)
backend.name

Оптимізація схеми для запуску на реальному бекенді — це багата і критично важлива тема. Але вона не є специфічною для VQE. Наразі просто нагадаємо два важливі терміни:

  • optimization_level: Описує, наскільки добре схема адаптована до топології обраного бекенда. Найнижчий рівень оптимізації виконує лише мінімум, необхідний для запуску схеми на пристрої: відображає кубіти схеми на кубіти пристрою і додає swap-гейти для всіх двокубітних операцій. Найвищий рівень оптимізації значно розумніший і використовує багато трюків для зменшення загальної кількості гейтів. Оскільки багатокубітні гейти мають високі рівні помилок і кубіти декогерують з часом, коротші схеми повинні давати кращі результати.
  • Dynamical Decoupling: Можна застосовувати послідовність гейтів до простоюючих кубітів. Це скасовує деякі небажані взаємодії з навколишнім середовищем. Зверніться до пов'язаної документації для отримання додаткової інформації про оптимізацію схем. Код нижче генерує менеджер пропусків за допомогою менеджерів попередньо встановлених пропусків з qiskit.transpiler.
from qiskit.transpiler import PassManager
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
PadDynamicalDecoupling,
ConstrainedReschedule,
)
from qiskit.circuit.library import XGate

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(target=target),
ConstrainedReschedule(
acquire_alignment=target.acquire_alignment,
pulse_alignment=target.pulse_alignment,
target=target,
),
PadDynamicalDecoupling(
target=target,
dd_sequence=[XGate(), XGate()],
pulse_alignment=target.pulse_alignment,
),
]
)

# Use the pass manager and draw the resulting circuit
ansatz_isa = pm.run(ansatz)
ansatz_isa.draw(output="mpl", idle_wires=False, style="iqp")

Output of the previous code cell

Аналогічно потрібно застосувати характеристики топології пристрою до гамільтоніана.

hamiltonian_isa = H.apply_layout(ansatz_isa.layout)
hamiltonian_isa
SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYXXII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXXXII'],
coeffs=[-0.09820182+0.j, -0.1740751 +0.j, -0.1740751 +0.j, 0.2242933 +0.j,
0.2242933 +0.j, 0.16891402+0.j, 0.1210099 +0.j, 0.16631441+0.j,
0.16631441+0.j, 0.1210099 +0.j, 0.17504456+0.j, 0.04530451+0.j,
0.04530451+0.j, 0.04530451+0.j, 0.04530451+0.j])

Крок 3: Виконання за допомогою примітивів Qiskit.

Перш ніж виконувати обчислення на обраному залізі, добре скористатися симулятором для швидкого налагодження і іноді для оцінки похибки. З цих причин коротко показуємо, як запустити VQE на симуляторі. Але критично важливо зазначити, що жоден класичний комп'ютер, симулятор чи GPU не може точно симулювати повну функціональність 127-кубітного квантового комп'ютера з сильним заплутуванням. В нинішню еру квантової корисності симулятори матимуть обмежене застосування.

Нагадаємо, що для кожного вибору параметрів варіаційної схеми потрібно обчислити значення математичного сподівання (оскільки саме воно мінімізується). Як ти вже, мабуть, здогадався(лась), найефективніший спосіб зробити це — використати примітив Qiskit, Estimator. Почнемо з локального симулятора, для якого потрібна локальна версія Estimator під назвою BackendEstimator.

Зберігаючи реальний бекенд, обраний для оптимізації, можна імпортувати модель шумового поведінки цього пристрою для використання з локальним симулятором. Тут ми використаємо aer_simulator_statevector.

# We will start by using a local simulator
from qiskit_aer import AerSimulator

# Import an estimator, this time from qiskit (we will import from Runtime for real hardware)
from qiskit.primitives import BackendEstimatorV2

# generate a simulator that mimics the real quantum system
backend_sim = AerSimulator.from_backend(backend)
estimator = BackendEstimatorV2(backend=backend_sim)

Нарешті час реалізувати VQE, мінімізуючи функцію вартості за допомогою обраного гамільтоніана, анзацу, класичного оптимізатора та нашого BackendEstimator на основі реального бекенда, обраного для подальшого використання. Зауважимо, що тут ми обрали відносно мале максимальне число ітерацій. Це тому, що ми лише використовуємо симулятор для налагодження. Кроки оптимізації VQE часто вимагають сотень ітерацій для збіжності.

res = minimize(
cost_func,
x0,
args=(ansatz_isa, hamiltonian_isa, estimator),
method="cobyla",
options={"maxiter": 10, "disp": True},
)

print(getattr(res, "fun") - nuclear_repulsion)
print(res)
Return from COBYLA because the objective function has been evaluated MAXFUN times.
Number of function values = 10 Least value of F = -0.11556938907226563
The corresponding X is:
[4.11796514 4.52126324 0.69570423 4.12781503 6.55507846 1.80713073
0.9645473 6.23812214]

-0.8355383835212453
message: Return from COBYLA because the objective function has been evaluated MAXFUN times.
success: False
status: 3
fun: -0.11556938907226563
x: [ 4.118e+00 4.521e+00 6.957e-01 4.128e+00 6.555e+00
1.807e+00 9.645e-01 6.238e+00]
nfev: 10
maxcv: 0.0

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

from qiskit_ibm_runtime import QiskitRuntimeService, Session
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime.options import EstimatorOptions

Серед іншого, використання сесії означає, що наше завдання буде очікувати в черзі лише один раз, на початку. Подальші ітерації класичного оптимізатора не потраплятимуть у чергу. У сесії можна встановити рівні стійкості та оптимізації. Ці інструменти достатньо важливі, щоб включити короткий огляд кожного та їх значення для VQE з посиланнями для подальшого вивчення:

  • Сесії Runtime: VQE є по суті ітеративним — класичний оптимізатор обирає нові варіаційні параметри, а отже, нові гейти використовуються в кожній наступній спробі. Без використання сесій це могло б призводити до додаткового часу очікування в черзі між кожною пробною схемою. Інкапсуляція обчислення VQE в сесію призводить до лише одного початкового очікування в черзі перед запуском завдання, але без додаткового часу між варіаційними кроками. Ця стратегія вже використовувалась у прикладі попереднього уроку, але може відігравати ще важливішу роль при зміні геометрії. Докладніше про сесії — в документації про режими виконання.
  • Вбудована оптимізація Estimator: У Estimator є вбудовані опції для оптимізації обчислення. У багатьох контекстах (включаючи Estimator) налаштування обмежені значеннями 0 і 1, де 0 означає відсутність оптимізації, а 1 (за замовчуванням) — деяку оптимізацію схеми під обране залізо. Деякі інші контексти дозволяють значення 0, 1, 2 або 3. Докладніше про конкретні методи, що використовуються при різних налаштуваннях, — в документації. Тут ми насправді встановимо оптимізацію на 0 і використаємо skip_transpilation = true, оскільки ми вже транспілювали нашу схему за допомогою менеджера пропусків вище в розділі оптимізації.
  • Вбудована стійкість Estimator: Як і з оптимізацією, Estimator має вбудовані налаштування стійкості до помилок, що відповідають різним підходам до пом'якшення помилок. Докладніше про налаштування рівня стійкості — в документації.

Варто зазначити, що пом'якшення помилок відіграє нюансовану роль у збіжності обчислення VQE. Класичний оптимізатор шукає в просторі параметрів ті параметри, що мінімізують енергію. Коли ти далеко від оптимальних параметрів, крутий градієнт може бути очевидним для класичного оптимізатора навіть в присутності помилок. Але в міру збіжності обчислення і наближення до оптимальних значень градієнт стає меншим і легше заглушується помилками. Скільки пом'якшення помилок використовувати? На яких етапах збіжності? Це вибір, який ти повинен зробити для свого конкретного застосування.

Для цього першого запуску на реальному залізі ми встановили стійкість на 0, щоб забезпечити відносно швидкий запуск. Для будь-якого серйозного застосування варто використовувати пом'якшення помилок. Зверни увагу, що в комірці нижче є два набори опцій: (1) опції для сесії Runtime, які ми назвали "session_options", і (2) опції для класичного оптимізатора, які тут просто називаються "options".

estimator_options = EstimatorOptions(resilience_level=0, default_shots=2000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)

res = minimize(
cost_func,
x0,
args=(ansatz_isa, hamiltonian_isa, estimator),
method="cobyla",
options={"maxiter": 10, "disp": True},
)
Return from COBYLA because the objective function has been evaluated MAXFUN times.
Number of function values = 10 Least value of F = -0.11691688904
The corresponding X is:
[5.11796514 5.52126324 0.69570423 5.12781503 6.55507846 1.80713073
1.9645473 6.23812214]

Переглянути хід виконання завдання можна на IBM Quantum® Platform у розділі Workloads.

print(getattr(res, "fun") - nuclear_repulsion)
print(res)
-0.8368858834889796
message: Return from COBYLA because the objective function has been evaluated MAXFUN times.
success: False
status: 3
fun: -0.11691688904
x: [ 5.118e+00 5.521e+00 6.957e-01 5.128e+00 6.555e+00
1.807e+00 1.965e+00 6.238e+00]
nfev: 10
maxcv: 0.0

Крок 4: Постобробка, повернення результату в класичному форматі.

Переконаємось, що правильно розуміємо ці виходи. Вихід "fun" — це мінімальне значення, отримане для функції вартості (не обов'язково останнє обчислене значення). Це повна енергія, включаючи позитивне ядерне відштовхування, тому ми також визначили electron_energy.

У наведеному вище випадку маємо повідомлення, що перевищено максимальну кількість обчислень функції, і що кількість обчислень функції (nfev) дорівнює 10. Це просто означає, що інші критерії збіжності оптимізації не були виконані; іншими словами, немає підстав вважати, що ми знайшли енергію основного стану. Це також є значенням success рівного "False".

Нарешті, маємо x. Це вектор варіаційних параметрів. Це параметри, використані в обчисленні, що дали мінімальну функцію вартості (значення математичного сподівання енергії). Ці вісім значень відповідають восьми кутам обертання у тих гейтах анзацу, які приймають змінні кути обертання.

Вітаємо! Ти виконав(ла) обчислення VQE на IBM Quantum QPU!

У наступному уроці ми побачимо, як адаптувати цей робочий процес для включення змінних у гамільтоніан. У контексті задач квантової хімії це може означати зміну геометрії для визначення форм молекул або місць зв'язування.

import qiskit
import qiskit_ibm_runtime

print(qiskit.version.get_version_info())
print(qiskit_ibm_runtime.version.get_version_info())
2.1.0
0.40.1