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

Порівняння налаштувань транспілятора

Орієнтовний час виконання: менше хвилини на процесорі Eagle r3 (УВАГА: це лише приблизна оцінка. Фактичний час може відрізнятися.)

Передумови

Щоб забезпечити швидші та ефективніші результати, починаючи з 1 березня 2024 року, схеми та спостережувані величини потрібно перетворювати так, щоб вони використовували лише інструкції, які підтримує QPU (квантовий процесор), перед відправкою до примітивів Qiskit Runtime. Такі схеми і спостережувані називаються схемами та спостережуваними архітектури набору інструкцій (ISA). Один із поширених способів це зробити — скористатися функцією generate_preset_pass_manager транспілятора. Втім, ти можеш обрати й більш ручний підхід.

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

Вимоги

Перед початком переконайся, що встановлено наступне:

  • Qiskit SDK v1.2 або новіше, з підтримкою візуалізації
  • Qiskit Runtime v0.28 або новіше (pip install qiskit-ibm-runtime)

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

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# Create circuit to test transpiler on
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import GroverOperator, Diagonal

# Use Statevector object to calculate the ideal output
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_histogram
from qiskit.transpiler import PassManager

from qiskit.circuit.library import XGate
from qiskit.quantum_info import hellinger_fidelity

# Qiskit Runtime
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Batch,
SamplerV2 as Sampler,
)
from qiskit_ibm_runtime.transpiler.passes.scheduling import (
ASAPScheduleAnalysis,
PadDynamicalDecoupling,
)

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

Створи невелику схему, яку транспілятор спробує оптимізувати. У цьому прикладі будується схема, що реалізує алгоритм Гровера з оракулом, що позначає стан 111. Далі симулюємо ідеальний розподіл (той, який ти отримав би, запустивши схему на ідеальному квантовому комп'ютері нескінченну кількість разів), щоб порівняти його з результатами пізніше.

# To run on hardware, select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
backend.name
'ibm_brisbanse'
oracle = Diagonal([1] * 7 + [-1])
qc = QuantumCircuit(3)
qc.h([0, 1, 2])
qc = qc.compose(GroverOperator(oracle))

qc.draw(output="mpl", style="iqp")

Output of the previous code cell

ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()

plot_histogram(ideal_distribution)

Output of the previous code cell

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

Далі транспілюємо схеми для QPU. Ти порівняєш продуктивність транспілятора зі значенням optimization_level рівним 0 (найнижчий) і 3 (найвищий). Найнижчий рівень оптимізації робить лише мінімально необхідне для запуску схеми на пристрої: відображає кубіти схеми на кубіти пристрою та додає вентилі SWAP для виконання всіх двокубітних операцій. Найвищий рівень оптимізації є значно розумнішим і використовує безліч прийомів для зменшення загальної кількості вентилів. Оскільки багатокубітні вентилі мають високий рівень помилок, а кубіти декогерують з часом, коротші схеми повинні давати кращі результати.

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

# Need to add measurements to the circuit
qc.measure_all()

# Find the correct two-qubit gate
twoQ_gates = set(["ecr", "cz", "cx"])
for gate in backend.basis_gates:
if gate in twoQ_gates:
twoQ_gate = gate

circuits = []
for optimization_level in [0, 3]:
pm = generate_preset_pass_manager(
optimization_level, backend=backend, seed_transpiler=0
)
t_qc = pm.run(qc)
print(
f"Two-qubit gates (optimization_level={optimization_level}): ",
t_qc.count_ops()[twoQ_gate],
)
circuits.append(t_qc)
Two-qubit gates (optimization_level=0):  21
Two-qubit gates (optimization_level=3): 14

Оскільки вентилі CNOT зазвичай мають високий рівень помилок, схема, транспільована з optimization_level=3, повинна працювати значно краще.

Ще один спосіб покращити продуктивність — динамічне роз'єднання: застосування послідовності вентилів до простоюючих кубітів. Це дозволяє нейтралізувати небажані взаємодії з навколишнім середовищем. Наступна комірка додає динамічне роз'єднання до схеми, транспільованої з optimization_level=3, і включає її до списку.

# Get gate durations so the transpiler knows how long each operation takes
durations = backend.target.durations()

# This is the sequence we'll apply to idling qubits
dd_sequence = [XGate(), XGate()]

# Run scheduling and dynamic decoupling passes on circuit
pm = PassManager(
[
ASAPScheduleAnalysis(durations),
PadDynamicalDecoupling(durations, dd_sequence),
]
)
circ_dd = pm.run(circuits[1])

# Add this new circuit to our list
circuits.append(circ_dd)
circ_dd.draw(output="mpl", style="iqp", idle_wires=False)

Output of the previous code cell

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

На цьому етапі у тебе є список схем, транспільованих для вказаного QPU. Далі створи екземпляр примітива sampler і запусти пакетне завдання за допомогою контекстного менеджера (with ...:), який автоматично відкриває та закриває пакет.

У межах контекстного менеджера виконай вибірку зі схем і збережи результати в result.

with Batch(backend=backend):
sampler = Sampler()
job = sampler.run(
[(circuit) for circuit in circuits], # sample all three circuits
shots=8000,
)
result = job.result()

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

Нарешті, побудуй графіки результатів, отриманих на пристрої, і порівняй їх з ідеальним розподілом. Видно, що результати з optimization_level=3 ближчі до ідеального розподілу завдяки меншій кількості вентилів, а optimization_level=3 + dd — ще ближчі завдяки динамічному роз'єднанню.

binary_prob = [
{
k: v / res.data.meas.num_shots
for k, v in res.data.meas.get_counts().items()
}
for res in result
]
plot_histogram(
binary_prob + [ideal_distribution],
bar_labels=False,
legend=[
"optimization_level=0",
"optimization_level=3",
"optimization_level=3 + dd",
"ideal distribution",
],
)

Output of the previous code cell

Ти можеш підтвердити це, обчисливши вірність Гелінгера між кожним набором результатів та ідеальним розподілом (чим вище, тим краще; 1 — ідеальна вірність).

for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")
0.848
0.945
0.990