Початок роботи з розрізанням схем за допомогою розрізів вентилів
Версії пакетів
Код на цій сторінці розроблено з використанням наступних залеж ностей. Рекомендуємо використовувати ці версії або новіші.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17
qiskit-addon-cutting~=0.10.0
Цей посібник демонструє два практичних приклади розрізання вентилів за допомогою пакету qiskit-addon-cutting. Перший приклад показує, як зменшити глибину схеми (кількість інструкцій схеми), розрізаючи вентилі заплутування на несуміжних кубітах, які б інакше призвели до накладних витрат у вигляді вентилів SWAP після транспіляції. Другий приклад охоплює використання розрізання вентилів для зменшення ширини схеми (кількості кубітів) шляхом розбиття схеми на кілька схем з меншою кількістю кубітів.
В обох прикладах використовується ансац efficient_su2 і відновлюється одне й те саме спостережуване.
Розрізання вентилів для зменшення глибини схеми
Наступний робочий процес зменшує глибину схеми шляхом розрізання далеких вентилів, уникаючи великої кількості вентилів SWAP, які б інакше були введені.
Почни з ансацу efficient_su2 із «круговим» заплутуванням для введення далеких вентилів.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
from qiskit.circuit.library import efficient_su2
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
from qiskit_ibm_runtime import SamplerV2, Batch
from qiskit_aer.primitives import EstimatorV2
from qiskit_addon_cutting import (
cut_gates,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)
circuit = efficient_su2(num_qubits=4, entanglement="circular")
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
print(f"Observable: {observable}")
circuit.draw("mpl", scale=0.8)
Observable: SparsePauliOp(['ZZII', 'IZZI', 'IIZZ', 'XIXI', 'ZIZZ', 'IXIX'],
coeffs=[ 1.+0.j, 1.+0.j, -1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
Кожен із вентилів CNOT між кубітами та вводить два вентилі SWAP після транспіляції (за умови, що кубіти з'єднані в лінію). Щоб уникнути такого збільшення глибини, можна замінити ці далекі вентилі об'єктами TwoQubitQPDGate за допомогою методу cut_gates(). Ця функція також повертає список екземплярів 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, яка приймає схему та спостережуване для відновлення.
Аргумент num_samples визначає, скільки вибірок брати з квазі-імовірнісного розподілу, і визначає точність коефіцієнтів, що використовуються для відновлення. Передача нескінченності (np.inf) гарантує точний розрахунок усіх коефіцієнтів. Читай документацію API про генерацію ваг та генерацію експериментів розрізання для отримання додаткової інформації.
Після того, як підексперименти згенеровані, їх можна транспілювати та використовувати примітив Sampler для вибірки з розподілу та відновлення оцінених очікуваних значень. Наступний блок коду генерує, транспілює та виконує підексперименти. Потім відновлює результати та порівнює їх із точним очікуваним значенням.
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observable.paulis, num_samples=np.inf
)
# Set a backend to use and transpile the subexperiments
backend = FakeManilaV2()
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend
)
isa_subexperiments = pass_manager.run(subexperiments)
# Set up the Qiskit Runtime Sampler primitive, submit the subexperiments, and retrieve the results
sampler = SamplerV2(backend)
job = sampler.run(isa_subexperiments, shots=4096 * 3)
results = job.result()
# Reconstruct the results
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
observable.paulis,
)
# Apply the coefficients of the original observable
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)
estimator = EstimatorV2()
exact_expval = (
estimator.run([(circuit, observable, [0.4] * len(circuit.parameters))])
.result()[0]
.data.evs
)
print(
f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}"
)
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(
f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}"
)
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.49812826
Exact expectation value: 0.50497603
Error in estimation: -0.00684778
Relative error in estimation: -0.0135606
Для точного відновлення очікуваного значення до виводу відновлення необхідно застосувати коефіцієнти оригінального спостережуваного (які відрізняються від коефіцієнтів у виводі generate_cutting_experiments()), оскільки ця інформація була втрачена під час генерації експериментів розрізання або розширення спостережуваного.
Зазвичай ці коефіцієнти можна застосувати через numpy.dot(), як показано вище.
Розрізання вентилів для зменшення ширини схеми
У цьому розділі демонструється використання розрізання вентилів для зменшення ширини схеми. Почни з того самого efficient_su2, але використовуй «лінійне» заплутування.
qc = efficient_su2(4, entanglement="linear", reps=2)
qc.assign_parameters([0.4] * len(qc.parameters), inplace=True)
observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
print(f"Observable: {observable}")
qc.draw("mpl", scale=0.8)
Observable: SparsePauliOp(['ZZII', 'IZZI', 'IIZZ', 'XIXI', 'ZIZZ', 'IXIX'],
coeffs=[ 1.+0.j, 1.+0.j, -1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
Потім згенеруй підсхеми та підспостережувані, які будеш виконувати, за допомогою функції partition_problem(). Ця функція приймає схему, спостережуване та необов'язкову схему розбиття, а повертає розрізані схеми та спостережувані у вигляді словника.
Розбиття визначається рядком міток у форматі "AABB", де кожна мітка в цьому рядку відповідає кубіту з тим самим індексом в аргументі circuit. Кубіти з однаковою міткою розбиття групуються разом, а будь-які нелокальні вентилі, що охоплюють більше одного розбиття, будуть розрізані.
Аргумент observables для partition_problem має тип PauliList. Коефіцієнти та фази членів спостережуваного ігноруються під час розкладання задачі та виконання підекспериментів. Їх можна застосувати повторно під час відновлення очікуваного значення.
partitioned_problem = partition_problem(
circuit=qc, partition_labels="AABB", observables=observable.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
print(f"Subobservables: {subobservables}")
subcircuits["A"].draw("mpl", scale=0.8)
Sampling overhead: 81.0
Subobservables: {'A': PauliList(['II', 'ZI', 'ZZ', 'XI', 'ZZ', 'IX']), 'B': PauliList(['ZZ', 'IZ', 'II', 'XI', 'ZI', 'IX'])}
subcircuits["B"].draw("mpl", scale=0.8)
Наступним кроком є використання підсхем та підспостережуваних для генерації підекспериментів, які будуть виконуватись на QPU за допомогою методу generate_cutting_experiments.
Для оцінки очікуваного значення повнорозмірної схеми з спільного квазі-імовірнісного розподілу розкладених вентилів генерується велика кількість підекспериментів, які потім виконуються на одному або кількох QPU. Кількість вибірок з цього розподілу контролюється аргументом num_samples.
Наступний блок коду генерує підексперименти та виконує їх за допомогою примітива Sampler на локальному симуляторі. (Щоб запустити їх на QPU, зміни backend на обраний ресурс QPU.)
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)
# Set a backend to use and transpile the subexperiments
backend = FakeManilaV2()
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend
)
isa_subexperiments = {
label: pass_manager.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
# Submit each partition's subexperiments to the Qiskit Runtime Sampler
# primitive, in a single batch so that the jobs will run back-to-back.
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=4096 * 3)
for label, subsystem_subexpts in isa_subexperiments.items()
}
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
Нарешті, очікуване значення повної схеми відновлюється за допомогою методу reconstruct_expectation_values.
Наведений нижче блок коду відновлює результати та порівнює їх із точним очікуваним значенням.
# Get expectation values for each observable term
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
# Reconstruct final expectation value
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)
estimator = EstimatorV2()
exact_expval = (
estimator.run([(qc, observable, [0.4] * len(qc.parameters))])
.result()[0]
.data.evs
)
print(
f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}"
)
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(
f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}"
)
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.53571896
Exact expectation value: 0.56254612
Error in estimation: -0.02682716
Relative error in estimation: -0.04768882
Наступні кроки
- Читай посібник Початок роботи з розрізанням схем за допомогою розрізів дротів.
- Читай статтю arXiv про розрізання схем із класичним зв'язком.