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

Вступ до дробових воріт

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

Загальна інформація

Дробові ворота на IBM QPU

Дробові ворота - це параметризовані квантові ворота, які дозволяють пряме виконання обертань під довільними кутами (в межах певних обмежень), усуваючи необхідність розкладати їх на кілька базисних воріт. Використовуючи природні взаємодії між фізичними кубітами, користувачі можуть реалізовувати певні унітарні оператори більш ефективно на обладнанні.

IBM Quantum® Heron QPU підтримують наступні дробові ворота:

  • RZZ(θ)R_{ZZ}(\theta) для 0<θ<π/20 < \theta < \pi / 2
  • RX(θ)R_X(\theta) для будь-якого дійсного значення θ\theta

Ці ворота можуть значно зменшити як глибину, так і тривалість квантових схем. Вони особливо вигідні в застосуваннях, які значною мірою покладаються на RZZR_{ZZ} та RXR_X, таких як гамільтонова симуляція, Алгоритм квантової приблизної оптимізації (QAOA) та методи квантових ядер. У цьому підручнику ми зосереджуємося на квантовому ядрі як практичному прикладі.

Обмеження

Дробові ворота наразі є експериментальною функцією і мають кілька обмежень:

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

Дивіться наступні джерела для отримання більш детальної інформації про дробові ворота.

Огляд

Робочий процес для використання дробових воріт загалом слідує робочому процесу шаблонів Qiskit. Ключова відмінність полягає в тому, що всі кути RZZ повинні задовольняти обмеження 0<θπ/20 < \theta \leq \pi/2. Існує два підходи для забезпечення виконання цієї умови. Цей підручник зосереджується на другому підході та рекомендує його.

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

1. Генерація значень параметрів, які задовольняють обмеження кута RZZ

Якщо Ви впевнені, що всі кути RZZ потрапляють у допустимий діапазон, Ви можете слідувати стандартному робочому процесу шаблонів Qiskit. У цьому випадку Ви просто надаєте значення параметрів як частину PUB. Робочий процес виглядає наступним чином.

pm = generate_preset_pass_manager(backend=backend, ...)
t_circuit = pm.run(circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit, parameter_values)])
estimator.run([(t_circuit, t_observable, parameter_values)])

Якщо Ви спробуєте надіслати PUB, який включає ворота RZZ з кутом за межами допустимого діапазону, Ви отримаєте повідомлення про помилку, таке як:

'The instruction rzz is supported only for angles in the range [0, pi/2], but an angle (20.0) outside of this range has been requested; via parameter value(s) γ[0]=10.0, substituted in parameter expression 2.0*γ[0].'

Щоб уникнути цієї помилки, Вам слід розглянути другий підхід, описаний нижче.

2. Призначення значень параметрів схемам перед транспіляцією

Пакет qiskit-ibm-runtime надає спеціалізований прохід транспілятора під назвою FoldRzzAngle. Цей прохід трансформує квантові схеми так, щоб всі кути RZZ відповідали обмеженню кута RZZ. Якщо Ви надаєте backend до generate_preset_pass_manager або transpile, Qiskit автоматично застосовує FoldRzzAngle до квантових схем. Це вимагає від Вас призначити значення параметрів квантовим схемам перед транспіляцією. Робочий процес виглядає наступним чином.

pm = generate_preset_pass_manager(backend=backend, ...)
b_circuit = circuit.assign_parameters(parameter_values)
t_circuit = pm.run(b_circuit)
t_observable = observable.apply_layout(t_circuit.layout)
sampler.run([(t_circuit,)])
estimator.run([(t_circuit, t_observable)])

Зверніть увагу, що цей робочий процес вимагає більших обчислювальних витрат, ніж перший підхід, оскільки він включає призначення значень параметрів квантовим схемам та зберігання схем із прив'язаними параметрами локально. Крім того, існує відома проблема в Qiskit, де трансформація воріт RZZ може не вдатися в певних сценаріях. Для обхідного шляху, будь ласка, зверніться до розділу Усунення несправностей. Цей підручник демонструє, як використовувати дробові ворота через другий підхід на прикладі, натхненному методом квантових ядер. Щоб краще зрозуміти, де квантові ядра, ймовірно, будуть корисними, ми рекомендуємо прочитати Liu, Arunachalam & Temme (2021).

Ви також можете ознайомитися з підручником Навчання квантових ядер та уроком Квантові ядра у курсі Квантового машинного навчання на IBM Quantum Learning.

Вимоги

Перед початком цього підручника переконайтеся, що у Вас встановлено наступне:

  • Qiskit SDK v2.0 або новіший, з підтримкою візуалізації
  • Qiskit Runtime v0.37 або новіший (pip install qiskit-ibm-runtime)
  • Qiskit Basis Constructor (pip install qiskit_basis_constructor)

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

import matplotlib.pyplot as plt
import numpy as np
from qiskit import QuantumCircuit, generate_preset_pass_manager
from qiskit.circuit import ParameterVector
from qiskit.circuit.library import unitary_overlap
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2

Увімкнення дробових воріт та перевірка базисних воріт

Щоб використовувати дробові ворота, Ви можете отримати backend, який їх підтримує, встановивши опцію use_fractional_gates=True. Якщо backend підтримує дробові ворота, Ви побачите rzz та rx серед його базисних воріт.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
) # backend should be a heron device or later
backend_name = backend.name
backend_c = service.backend(backend_name) # w/o fractional gates
backend_f = service.backend(
backend_name, use_fractional_gates=True
) # w/ fractional gates
print(f"Backend: {backend_name}")
print(f"No fractional gates: {backend_c.basis_gates}")
print(f"With fractional gates: {backend_f.basis_gates}")
if "rzz" not in backend_f.basis_gates:
print(f"Backend {backend_name} does not support fractional gates")
Backend: ibm_fez
No fractional gates: ['cz', 'id', 'rz', 'sx', 'x']
With fractional gates: ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x']

Робочий процес з дробовими воротами

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

Схема квантового ядра

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

Ми починаємо з побудови квантової схеми для обчислення окремих елементів матриці ядра. Це виконується шляхом поєднання схем карти ознак ZZ з унітарним перекриттям. Функція ядра приймає вектори в просторі, відображеному на ознаки, і повертає їх внутрішній добуток як елемент матриці ядра: K(x,y)=Φ(x)Φ(y),K(x, y) = \langle \Phi(x) | \Phi(y) \rangle, де Φ(x)|\Phi(x)\rangle представляє квантовий стан, відображений на ознаки.

Ми вручну конструюємо схему карти ознак ZZ з використанням воріт RZZ. Хоча Qiskit надає вбудований zz_feature_map, він наразі не підтримує ворота RZZ станом на Qiskit v2.0.2 (див. проблему).

Далі ми обчислюємо функцію ядра для ідентичних входів - наприклад, K(x,x)=1K(x, x) = 1. На зашумлених квантових комп'ютерах це значення може бути меншим за 1 через шум. Результат, ближчий до 1, вказує на менший шум під час виконання. У цьому підручнику ми називаємо це значення точністю, визначеною як fidelity=K(x,x).\text{fidelity} = K(x, x).

optimization_level = 2
shots = 2000
reps = 3
rng = np.random.default_rng(seed=123)
def my_zz_feature_map(num_qubits: int, reps: int = 1) -> QuantumCircuit:
x = ParameterVector("x", num_qubits * reps)
qc = QuantumCircuit(num_qubits)
qc.h(range(num_qubits))
for k in range(reps):
K = k * num_qubits
for i in range(num_qubits):
qc.rz(x[i + K], i)
pairs = [(i, i + 1) for i in range(num_qubits - 1)]
for i, j in pairs[0::2] + pairs[1::2]:
qc.rzz((np.pi - x[i + K]) * (np.pi - x[j + K]), i, j)
return qc

def quantum_kernel(num_qubits: int, reps: int = 1) -> QuantumCircuit:
qc = my_zz_feature_map(num_qubits, reps=reps)
inner_product = unitary_overlap(qc, qc, "x", "y", insert_barrier=True)
inner_product.measure_all()
return inner_product

def random_parameters(inner_product: QuantumCircuit) -> np.ndarray:
return np.tile(rng.random(inner_product.num_parameters // 2), 2)

def fidelity(result) -> float:
ba = result.data.meas
return ba.get_int_counts().get(0, 0) / ba.num_shots

Схеми квантових ядер та відповідні їм значення параметрів генеруються для систем від 4 до 40 кубітів, і їхні точності згодом оцінюються.

qubits = list(range(4, 44, 4))
circuits = [quantum_kernel(i, reps=reps) for i in qubits]
params = [random_parameters(circ) for circ in circuits]

Схема з чотирма кубітами візуалізована нижче.

circuits[0].draw("mpl", fold=-1)

Output of the previous code cell

У стандартному робочому процесі шаблонів Qiskit значення параметрів зазвичай передаються примітиву Sampler або Estimator як частина PUB. Однак, при використанні backend, який підтримує дробові ворота, ці значення параметрів повинні бути явно призначені квантовій схемі перед транспіляцією.

b_qc = [
circ.assign_parameters(param) for circ, param in zip(circuits, params)
]
b_qc[0].draw("mpl", fold=-1)

Output of the previous code cell

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

Потім ми транспілюємо схему, використовуючи менеджер проходів, слідуючи стандартному шаблону Qiskit. Надаючи backend, який підтримує дробові ворота, до generate_preset_pass_manager, спеціалізований прохід під назвою FoldRzzAngle автоматично включається. Цей прохід модифікує схему для відповідності обмеженням кута RZZ. У результаті ворота RZZ з від'ємними значеннями на попередньому рисунку трансформуються в додатні значення, і додаються деякі додаткові ворота X.

backend_f = service.backend(name=backend_name, use_fractional_gates=True)
# pm_f includes `FoldRzzAngle` pass
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_f
)
t_qc_f = pm_f.run(b_qc)
print(t_qc_f[0].count_ops())
t_qc_f[0].draw("mpl", fold=-1)
OrderedDict([('rz', 35), ('rzz', 18), ('x', 13), ('rx', 9), ('measure', 4), ('barrier', 2)])

Output of the previous code cell

Щоб оцінити вплив дробових воріт, ми оцінюємо кількість нелокальних воріт (CZ та RZZ для цього backend), разом із глибиною схем та тривалістю, і порівнюємо ці метрики з метриками зі стандартного робочого процесу пізніше.

nnl_f = [qc.num_nonlocal_gates() for qc in t_qc_f]
depth_f = [qc.depth() for qc in t_qc_f]
duration_f = [
qc.estimate_duration(backend_f.target, unit="u") for qc in t_qc_f
]

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

Ми запускаємо транспільовану схему з backend, який підтримує дробові ворота.

sampler_f = SamplerV2(mode=backend_f)
sampler_f.options.dynamical_decoupling.enable = True
sampler_f.options.dynamical_decoupling.sequence_type = "XY4"
sampler_f.options.dynamical_decoupling.skip_reset_qubits = True
job = sampler_f.run(t_qc_f, shots=shots)
print(job.job_id())
d4bninsi51bc738j97eg

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

Ви можете отримати значення функції ядра K(x,x)K(x, x) шляхом вимірювання ймовірності бітового рядка з усіма нулями 00...00 у виході.

# job = service.job("d1obougt0npc73flhiag")
result = job.result()
fidelity_f = [fidelity(result=res) for res in result]
print(fidelity_f)
usage_f = job.usage()
[0.9005, 0.647, 0.3345, 0.355, 0.3315, 0.174, 0.1875, 0.149, 0.1175, 0.085]

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

У цьому розділі ми представляємо стандартний робочий процес шаблонів Qiskit з використанням backend, який не підтримує дробові ворота. Порівнюючи транспільовані схеми, Ви помітите, що версія з використанням дробових воріт (з попереднього розділу) є більш компактною, ніж версія без дробових воріт.

# step 1: map classical inputs to quantum problem
# `circuits` and `params` from the previous section are reused here
# step 2: optimize circuits
backend_c = service.backend(backend_name) # w/o fractional gates
pm_c = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_c
)
t_qc_c = pm_c.run(circuits)
print(t_qc_c[0].count_ops())
t_qc_c[0].draw("mpl", fold=-1)
OrderedDict([('rz', 130), ('sx', 80), ('cz', 36), ('measure', 4), ('barrier', 2)])

Output of the previous code cell

nnl_c = [qc.num_nonlocal_gates() for qc in t_qc_c]
depth_c = [qc.depth() for qc in t_qc_c]
duration_c = [
qc.estimate_duration(backend_c.target, unit="u") for qc in t_qc_c
]
# step 3: execute
sampler_c = SamplerV2(backend_c)
sampler_c.options.dynamical_decoupling.enable = True
sampler_c.options.dynamical_decoupling.sequence_type = "XY4"
sampler_c.options.dynamical_decoupling.skip_reset_qubits = True
job = sampler_c.run(pubs=zip(t_qc_c, params), shots=shots)
print(job.job_id())
d4bnirvnmdfs73ae3a2g
# step 4: post-processing
# job = service.job("d1obp8j3rr0s73bg4810")
result = job.result()
fidelity_c = [fidelity(res) for res in result]
print(fidelity_c)
usage_c = job.usage()
[0.6675, 0.5725, 0.098, 0.102, 0.065, 0.0235, 0.006, 0.0015, 0.0015, 0.002]

Порівняння глибини та точності

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

plt.plot(qubits, depth_c, "-o", label="no fractional gates")
plt.plot(qubits, depth_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("depth")
plt.title("Comparison of depths")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bcaac50>

Output of the previous code cell

plt.plot(qubits, duration_c, "-o", label="no fractional gates")
plt.plot(qubits, duration_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("duration (µs)")
plt.title("Comparison of durations")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bdef310>

Output of the previous code cell

plt.plot(qubits, nnl_c, "-o", label="no fractional gates")
plt.plot(qubits, nnl_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("number of non-local gates")
plt.title("Comparison of numbers of non-local gates")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12be8ac90>

Output of the previous code cell

plt.plot(qubits, fidelity_c, "-o", label="no fractional gates")
plt.plot(qubits, fidelity_f, "-o", label="with fractional gates")
plt.xlabel("number of qubits")
plt.ylabel("fidelity")
plt.title("Comparison of fidelities")
plt.grid()
plt.legend()
<matplotlib.legend.Legend at 0x12bea8290>

Output of the previous code cell

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

print(f"no fractional gates: {usage_c} seconds")
print(f"fractional gates: {usage_f} seconds")
no fractional gates: 7 seconds
fractional gates: 7 seconds

Додаткова тема: Використання лише дробових RX гейтів

Необхідність модифікованого робочого процесу при використанні дробових гейтів в основному випливає з обмеження на кути RZZ гейтів. Однак, якщо Ви використовуєте лише дробові RX гейти та виключаєте дробові RZZ гейти, Ви можете продовжувати дотримуватися стандартного робочого процесу Qiskit patterns. Цей підхід все ще може запропонувати значні переваги, особливо в схемах, які містять велику кількість RX гейтів та U гейтів, зменшуючи загальну кількість гейтів і потенційно покращуючи продуктивність. У цьому розділі ми демонструємо, як оптимізувати Ваші схеми, використовуючи лише дробові RX гейти, виключаючи RZZ гейти.

Для підтримки цього ми надаємо допоміжну функцію, яка дозволяє Вам вимкнути конкретний базовий гейт в об'єкті Target. Тут ми використовуємо її для вимкнення RZZ гейтів.

from qiskit.circuit.library import n_local
from qiskit.transpiler import Target
def remove_instruction_from_target(target: Target, gate_name: str) -> Target:
new_target = Target(
description=target.description,
num_qubits=target.num_qubits,
dt=target.dt,
granularity=target.granularity,
min_length=target.min_length,
pulse_alignment=target.pulse_alignment,
acquire_alignment=target.acquire_alignment,
qubit_properties=target.qubit_properties,
concurrent_measurements=target.concurrent_measurements,
)

for name, qarg_map in target.items():
if name == gate_name:
continue
instruction = target.operation_from_name(name)
if qarg_map == {None: None}:
qarg_map = None
new_target.add_instruction(instruction, qarg_map, name=name)
return new_target

Ми використовуємо схему, що складається з U, CZ та RZZ гейтів, як приклад.

qc = n_local(3, "u", "cz", "linear", reps=1)
qc.rzz(1.1, 0, 1)
qc.draw("mpl")

Output of the previous code cell

Спочатку ми транспілюємо схему для бекенду, який не підтримує дробові гейти.

pm_c = generate_preset_pass_manager(
optimization_level=optimization_level, backend=backend_c
)
t_qc = pm_c.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 23), ('sx', 16), ('cz', 4)])

Output of the previous code cell

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

backend_f = service.backend(backend_name, use_fractional_gates=True)
target = remove_instruction_from_target(backend_f.target, "rzz")
pm_f = generate_preset_pass_manager(
optimization_level=optimization_level,
target=target,
)
t_qc = pm_f.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 22), ('sx', 14), ('cz', 4), ('rx', 1)])

Output of the previous code cell

Оптимізація U гейтів з дробовими RX гейтами

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

Вам потрібно буде встановити пакет qiskit-basis-constructor для цього розділу. Це бета-версія нового плагіна транспіляції для Qiskit, який може бути інтегрований в Qiskit у майбутньому.

# %pip install qiskit-basis-constructor
from qiskit.circuit.library import UGate
from qiskit_basis_constructor import DEFAULT_EQUIVALENCE_LIBRARY

Ми транспілюємо схему, використовуючи лише дробові RX гейти, виключаючи RZZ гейти. Вводячи користувацьке правило декомпозиції, як показано нижче, ми можемо зменшити кількість однокубітних гейтів, необхідних для реалізації U гейта.

Ця функція наразі обговорюється в цій GitHub issue.

# special decomposition rule for UGate
x = ParameterVector("x", 3)
zxz = QuantumCircuit(1)
zxz.rz(x[2] - np.pi / 2, 0)
zxz.rx(x[0], 0)
zxz.rz(x[1] + np.pi / 2, 0)
DEFAULT_EQUIVALENCE_LIBRARY.add_equivalence(UGate(x[0], x[1], x[2]), zxz)

Далі ми застосовуємо транспілер, використовуючи трансляцію constructor-beta, надану пакетом qiskit-basis-constructor. В результаті загальна кількість гейтів зменшується порівняно з попередньою транспіляцією.

pm_f = generate_preset_pass_manager(
optimization_level=optimization_level,
target=target,
translation_method="constructor-beta",
)
t_qc = pm_f.run(qc)
print(t_qc.count_ops())
t_qc.draw("mpl")
OrderedDict([('rz', 16), ('rx', 9), ('cz', 4)])

Output of the previous code cell

Усунення несправностей

Проблема: Недійсні кути RZZ можуть залишитися після транспіляції

Станом на Qiskit v2.0.3 існують відомі проблеми, коли RZZ гейти з недійсними кутами можуть залишатися в схемах навіть після транспіляції. Проблема зазвичай виникає за наступних умов.

Помилка при використанні опції target з generate_preset_pass_manager або transpiler

Коли опція target використовується з generate_preset_pass_manager або transpiler, спеціалізований проход транспілера FoldRzzAngle не викликається. Для забезпечення правильної обробки кутів RZZ для дробових гейтів, ми рекомендуємо завжди використовувати опцію backend замість цього. Дивіться цю issue для більш детальної інформації.

Помилка, коли схеми містять певні гейти

Якщо Ваша схема включає певні гейти, такі як XXPlusYYGate, транспілер Qiskit може генерувати RZZ гейти з недійсними кутами. Якщо Ви зіткнулися з цією проблемою, дивіться цю GitHub issue для обхідного рішення.

Опитування щодо навчального посібника

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

Link to survey