Розрізання схем для зменшення глибини
Орієнтовний час виконання: вісім хвилин на процесорі Eagle (ПРИМІТКА: Це лише орієнтовна оцінка. Фактичний час виконання може відрізнятися.)
Передумови
Цей посібник демонструє, як побудувати Qiskit pattern для розрізання вентилів у квантовій схемі з метою зменшення глибини схеми. Для більш детального обговорення розрізання схем відвідай документацію додатка Qiskit для розрізання схем.
Вимоги
Перед початком роботи з цим посібником переконайся, що у тебе встановлено наступне:
- Qiskit SDK v2.0 або новішої версії з підтримкою візуалізації
- Qiskit Runtime v0.22 або новішої версії (
pip install qiskit-ibm-runtime) - Додаток Qiskit для розрізання схем v0.9.0 або новішої версії (
pip install qiskit-addon-cutting)
Налаштування
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
import numpy as np
from qiskit.circuit.library import EfficientSU2
from qiskit.quantum_info import PauliList, Statevector, SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
Крок 1: Відображення класичних вхідних даних на квантову задачу
Ми реалізуємо наш шаблон Qiskit, використовуючи чотири кроки, описані в документації. У цьому випадку ми будемо симулювати очікувані значення на схемі певної глибини шляхом розрізання вентилів, що призводить до swap-вентилів, та виконання підексперименів на схемах меншої глибини. Розрізання вентилів є актуальним для кроків 2 (оптимізація схеми для квантового виконання шляхом декомпозиції віддалених вентилів) та 4 (постобробка для реконструкції очікуваних значень на оригінальній схемі). На першому кроці ми згенеруємо схему з бібліотеки схем Qiskit та визначимо деякі спостережувані величини.
- Вхідні дані: Класичні параметри для визначення схеми
- Вихідні дані: Абстрактна схема та спостережувані величини
circuit = EfficientSU2(num_qubits=4, entanglement="circular").decompose()
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
observables = PauliList(["ZZII", "IZZI", "IIZZ", "XIXI", "ZIZZ", "IXIX"])
circuit.draw("mpl", scale=0.8, style="iqp")
Крок 2: Оптимізація задачі для виконання на квантовому обладнанні
- Вхідні дані: Абстрактна схема та спостережувані величини
- Вихідні дані: Цільова схема та спостережувані величини, отримані шляхом розрізання віддалених вентилів для зменшення глибини транспільованої схеми
Ми обираємо початкову розкладку, яка вимагає двох swap-операцій для виконання вентилів між кубітами 3 та 0, і ще двох swap-операцій для повернення кубітів у їхні початкові позиції. Ми обираємо optimization_level=3, що є найвищим рівнем оптимізації, доступним у попередньо налаштованому менеджері проходів.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=circuit.num_qubits, simulator=False
)
pm = generate_preset_pass_manager(
optimization_level=3, initial_layout=[0, 1, 2, 3], backend=backend
)
transpiled_qc = pm.run(circuit)

print(f"Transpiled circuit depth: {transpiled_qc.depth()}")
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, style="iqp", fold=-1)
Transpiled circuit depth: 103
Знаходження та розрізання віддалених вентилів: Ми замінимо віддалені вентилі (вентилі, що з'єднують нелокальні кубіти, 0 та 3) об'єктами TwoQubitQPDGate, вказавши їхні індекси. cut_gates замінить вентилі за вказаними індексами об'єктами TwoQubitQPDGate, а також поверне список екземплярів QPDBasis — по одному для кожної декомпозиції вентиля. Об'єкт QPDBasis містить інформацію про те, як декомпозувати розрізані вентилі на однокубітні операції.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]
# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)
qpd_circuit.draw("mpl", scale=0.8)
Генерація підексперименів для виконання на бекенді: generate_cutting_experiments приймає схему, що містить екземпляри TwoQubitQPDGate, та спостережувані величини у вигляді PauliList.
Для симуляції очікуваного значення повнорозмірної схеми з квазіймовірнісного розподілу декомпозованих вентилів генерується багато підексперименів, які потім виконуються на одному або кількох бекендах. Кількість зразків, взятих з розподілу, контролюється параметром num_samples, і для кожного унікального зразка надається один комбінований коефіцієнт. Для отримання додаткової інформації про обчислення коефіцієнтів звернись до пояснювальних матеріалів.
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observables, num_samples=np.inf
)
Для порівняння: підексперименти QPD будуть мати меншу глибину після розрізання віддалених вентилів: Ось приклад довільно обраного підексперименту, згенерованого зі схеми QPD. Його глибина була зменшена більш ніж удвічі. Необхідно згенерувати та обчислити багато таких імовірнісних підексперименів для реконструкції очікуваного значення більш глибокої схеми.
# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pm.run(subexperiments[100])
print(f"Original circuit depth after transpile: {transpiled_qc.depth()}")
print(
f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth()}"
)
transpiled_qpd_circuit.draw(
"mpl", scale=0.6, style="iqp", idle_wires=False, fold=-1
)
Original circuit depth after transpile: 103
QPD subexperiment depth after transpile: 46
З іншого боку, розрізання призводить до необхідності додаткового семплювання. У цьому випадку ми розрізали три вентилі CNOT, що призвело до накладних витрат на семплювання у розмірі . Для отримання додаткової інформації про накладні витрати на семплювання, спричинені розрізанням схем, звернись до документації Circuit Knitting Toolbox.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
Крок 3: Виконання за допомогою примітивів Qiskit
Виконай цільові схеми ("підексперименти") за допомогою примітива Sampler.
- Вхідні дані: Цільові схеми
- Вихідні дані: Квазіймовірнісні розподіли
# Transpile the subexperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pm.run(subexperiments)
# Set up the Qiskit Runtime Sampler primitive. For a fake backend, this will use a local simulator.
sampler = SamplerV2(backend)
# Submit the subexperiments
job = sampler.run(isa_subexperiments)
# Retrieve the results
results = job.result()
print(job.job_id())
czypg1r6rr3g008mgp6g
Крок 4: Постобробка та повернення результату в бажаному класичному форматі
Використай результати підексперименів, підспостережувані величини та коефіцієнти семплювання для реконструкції очікуваного значення оригінальної схеми.
Вхідні дані: Квазіймовірнісні розподіли Вихідні дані: Реконструйовані очікувані значення
reconstructed_expvals = reconstruct_expectation_values(
results,
coefficients,
observables,
)
# Reconstruct final expectation value
final_expval = np.dot(reconstructed_expvals, [1] * len(observables))
print("Final reconstructed expectation value")
print(final_expval)
Final reconstructed expectation value
1.0751342773437473
ideal_expvals = [
Statevector(circuit).expectation_value(SparsePauliOp(observable))
for observable in observables
]
print("Ideal expectation value")
print(np.dot(ideal_expvals, [1] * len(observables)).real)
Ideal expectation value
1.2283177520039992
Опитування щодо посібника
Будь ласка, пройди це коротке опитування, щоб надати зворотний зв'язок щодо цього посібника. Твої відгуки допоможуть нам покращити наш контент та досвід користувачів.
Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.