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

Поєднання методів пом'якшення помилок із примітивом Estimator

Оцінка використання: 7 хвилин на процесорі Heron r2 (ПРИМІТКА: це лише оцінка. Ваш час виконання може відрізнятися.)

Результати навчання

Ми рекомендуємо користувачам ознайомитися з такими темами перед проходженням цього посібника:

  • Основи динамічного розв'язання, пом'якшення помилок вимірювання, гейт-твірлінгу та екстраполяції нульового шуму, описані в цьому посібнику.

Передумови

Після проходження цього посібника користувачі повинні розуміти:

  • Як вищезазначені методи пом'якшення помилок вибірково реалізуються на обладнанні.
  • Як вони порівнюються з точки зору здатності пом'якшувати апаратний шум.

Загальні відомості

Цей посібник досліджує параметри придушення та пом'якшення помилок, доступні з примітивом Estimator від Qiskit Runtime. Він показує, як реалізувати кожен із наступних методів окремо:

  • Динамічне розв'язання (Dynamical decoupling)
  • Пом'якшення помилок вимірювання
  • Гейт-твірлінг (Gate twirling)
  • Екстраполяція нульового шуму (ZNE)

Зверни увагу, що альтернативою окремій реалізації цих технік є їх реалізація за допомогою рівня стійкості, де resilience_level приймає значення 0, 1, 2:

  • 0 : Пом'якшення не реалізовано.
  • 1 : Реалізовано пом'якшення помилок вимірювання.
  • 2 : Реалізовано гейт-твірлінг, пом'якшення помилок вимірювання та ZNE.

У цьому посібнику ти побудуєш схему та спостережувану величину та надішлеш завдання за допомогою примітива Estimator з різними комбінаціями налаштувань пом'якшення помилок. Потім ти побудуєш графіки результатів, щоб спостерігати вплив різних налаштувань. Більшість посібника використовує 10-кубітну схему для зручності візуалізації, а наприкінці ти зможеш масштабувати робочий процес до 50 кубітів.

Вимоги

Перш ніж розпочати цей покроковий посібник, переконайся, що у тебе встановлено наступне:

  • Qiskit SDK v2.1 або новіша версія з підтримкою візуалізації
  • Qiskit Runtime v0.40 або новіша версія (pip install qiskit-ibm-runtime)

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

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
import numpy as np

from qiskit.circuit.library import efficient_su2, unitary_overlap
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Batch, EstimatorV2 as Estimator

Приклад на невеликому симуляторі

Ми пропустимо цей крок, оскільки пом'якшення помилок під час виконання не підтримується на симуляторах.

Приклад на обладнанні

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

У цьому покроковому посібнику передбачається, що класична задача вже була відображена на квантову. Почни з побудови схеми та спостережуваної величини для вимірювання. Хоча методи, що використовуються тут, застосовні до багатьох різних типів схем, для простоти в цьому посібнику використовується схема efficient_su2, що входить до бібліотеки схем Qiskit.

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

n_qubits = 10
reps = 1

circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)

circuit.decompose().draw("mpl", scale=0.7)

Вивід попередньої комірки коду

Як спостережувану величину візьмемо оператор Паулі ZZ, що діє на останній кубіт, ZIIZ I \cdots I. Зверни увагу, що останній кубіт відповідає першому елементу цього рядка через використання Qiskit нотації з молодшим байтом спочатку.

# Z on the last qubit (index -1) with coefficient 1.0
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)

На цьому етапі ти міг би перейти до запуску схеми та вимірювання спостережуваної величини. Однак ти також хочеш порівняти результат квантового пристрою з правильною відповіддю — тобто теоретичним значенням спостережуваної величини, якби схема була виконана без помилок. Для невеликих квантових схем ти можеш обчислити це значення шляхом моделювання схеми на класичному комп'ютері, але це неможливо для більших схем утилітарного масштабу. Ти можеш обійти цю проблему за допомогою техніки «дзеркальної схеми» (також відомої як «обчислення-зворотне обчислення»), яка корисна для порівняльного аналізу продуктивності квантових пристроїв.

Дзеркальна схема

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

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

# Generate random parameters
rng = np.random.default_rng(1234)
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)

# Assign the parameters to the circuit
assigned_circuit = circuit.assign_parameters(params)

# Add a barrier to prevent circuit optimization of mirrored operators
assigned_circuit.barrier()

# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)

mirror_circuit.decompose().draw("mpl", scale=0.7)

Вивід попередньої комірки коду

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

Ти повинен оптимізувати свою схему перед запуском на обладнанні. Цей процес включає кілька кроків:

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

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

Тобі потрібно вибрати апаратний пристрій для використання перед оптимізацією схеми. Наступна комірка коду запитує найменш завантажений пристрій з щонайменше 127 кубітами.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
print(backend)
<IBMBackend('ibm_fez')>

Ти можеш транспілювати свою схему для обраного бекенда, створивши менеджер проходів, а потім запустивши його на схемі. Простий спосіб створити менеджер проходів — використати функцію generate_preset_pass_manager. Див. Транспіляція з менеджерами проходів для більш детального пояснення транспіляції з менеджерами проходів.

pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(mirror_circuit)

isa_circuit.draw("mpl", idle_wires=False, scale=0.7, fold=-1)

Вивід попередньої комірки коду

Транспільована схема тепер містить лише інструкції ISA. Всі гейти були розкладені у термінах гейтів X\sqrt{X} та обертань RzR_z, а також гейтів CZ.

Процес транспіляції відобразив віртуальні кубіти схеми на фізичні кубіти обладнання. Інформація про розкладку кубітів зберігається в атрибуті layout транспільованої схеми. Спостережувана величина також була визначена в термінах віртуальних кубітів, тому тобі потрібно застосувати цю розкладку до спостережуваної величини, що можна зробити за допомогою методу apply_layout класу SparsePauliOp.

isa_observable = observable.apply_layout(isa_circuit.layout)

print("Original observable:")
print(observable)
print()
print("Observable with layout applied:")
print(isa_observable)
Original observable:
SparsePauliOp(['ZIIIIIIIII'],
coeffs=[1.+0.j])

Observable with layout applied:
SparsePauliOp(['IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])

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

Тепер ти готовий запустити свою схему за допомогою примітива Estimator.

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

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

pub = (isa_circuit, isa_observable)

jobs = []

with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_SS"
] # add tag for this small scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0

# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)

# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)

# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)

# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)

# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)

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

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

# Retrieve the job results
results = [job.result() for job in jobs]

# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]

# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)

# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")

plt.show()

Вивід попередньої комірки коду

На такому невеликому масштабі важко побачити вплив більшості методів пом'якшення помилок, але екстраполяція нульового шуму дає помітне покращення. Однак зверни увагу, що це покращення не є безкоштовним, оскільки результат ZNE також має більшу планку похибки.

Приклад на обладнанні великого масштабу

При розробці експерименту корисно починати з невеликої схеми, щоб полегшити візуалізацію та моделювання. Тепер, коли ти розробив та протестував робочий процес на 10-кубітній схемі, ти можеш масштабувати його до 50 кубітів. Наступна комірка коду повторює всі кроки цього посібника, але тепер застосовує їх до 50-кубітної схеми.

n_qubits = 50
reps = 1

# Construct circuit and observable
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)

# Assign parameters to circuit
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
assigned_circuit = circuit.assign_parameters(params)
assigned_circuit.barrier()

# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)

# Transpile circuit and observable
isa_circuit = pass_manager.run(mirror_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)

# Run jobs
pub = (isa_circuit, isa_observable)

jobs = []

with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_LS"
] # add tag for this large scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0

# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)

# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)

# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)

# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)

# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)

# Retrieve the job results
results = [job.result() for job in jobs]

# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]

# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)

# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")

plt.show()

Вивід попередньої комірки коду

Коли ти порівнюєш результати для 50 кубітів із результатами для 10 кубітів, отриманими раніше, ти можеш помітити наступне (твої результати можуть відрізнятися між запусками):

  • Усі експерименти дають результати ближчі до ідеального значення, і всі планки похибок менші.
  • Додавання динамічного розв'язання могло погіршити продуктивність порівняно з випадком без пом'якшення. Це не дивно, оскільки схема дуже щільна. Динамічне розв'язання найбільш корисне, коли в схемі є великі проміжки, протягом яких кубіти простоюють без застосування до них гейтів. Коли ці проміжки відсутні, динамічне розв'язання неефективне і може навіть погіршити продуктивність через помилки в самих імпульсах динамічного розв'язання. 10-кубітна схема могла бути занадто малою, щоб спостерігати цей ефект.
  • З екстраполяцією нульового шуму результат дуже близький до ідеального значення. Це демонструє потужність техніки ZNE.

Наступні кроки

Recommendations

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