Wire Cutting yang Dinyatakan sebagai Instruksi `Move` Dua-Qubit
Dalam tutorial ini, kita akan merekonstruksi nilai ekspektasi dari sebuah Circuit tujuh-Qubit dengan membaginya menjadi dua Circuit empat-Qubit menggunakan wire cutting.
Berikut adalah langkah-langkah yang akan kita lakukan dalam pola Qiskit ini:
- Langkah 1: Petakan masalah ke Circuit kuantum dan operator:
- Petakan hamiltonian ke dalam sebuah Circuit kuantum.
- Langkah 2: Optimalkan untuk hardware target [Menggunakan cutting addon]:
- Potong Circuit dan observable.
- Transpile subeksperimen untuk hardware.
- Langkah 3: Eksekusi pada hardware target:
- Jalankan subeksperimen yang diperoleh pada Langkah 2 menggunakan primitif
Sampler.
- Jalankan subeksperimen yang diperoleh pada Langkah 2 menggunakan primitif
- Langkah 4: Pasca-pemrosesan hasil [Menggunakan cutting addon]:
- Gabungkan hasil Langkah 3 untuk merekonstruksi nilai ekspektasi dari observable yang dimaksud.
Langkah 1: Pemetaanβ
Buat Circuit untuk dipotongβ
Pertama, kita mulai dengan sebuah Circuit yang terinspirasi dari Gambar 1(a) dalam arXiv:2302.03366v1.
# 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 import QuantumCircuit
qc_0 = QuantumCircuit(7)
for i in range(7):
qc_0.rx(np.pi / 4, i)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
qc_0.cx(3, 4)
qc_0.cx(3, 5)
qc_0.cx(3, 6)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
<qiskit.circuit.instructionset.InstructionSet at 0x7f16ab191a80>
qc_0.draw("mpl")

Tentukan sebuah observableβ
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp(["ZIIIIII", "IIIZIII", "IIIIIIZ"])
Langkah 2: Optimasiβ
Buat Circuit baru dengan instruksi Move yang ditempatkan pada lokasi pemotongan yang diinginkanβ
Berdasarkan Circuit di atas, kita ingin menempatkan dua wire cut pada garis Qubit tengah, sehingga Circuit dapat dipisahkan menjadi dua Circuit yang masing-masing terdiri dari empat Qubit. Salah satu caranya adalah dengan menempatkan secara manual instruksi Move dua-Qubit yang memindahkan state dari satu jalur Qubit ke jalur lainnya. Instruksi Move secara konseptual setara dengan operasi reset pada Qubit kedua, diikuti oleh gate SWAP. Efek instruksi ini adalah mentransfer state dari Qubit pertama (sumber) ke Qubit kedua (tujuan), sambil membuang state masuk dari Qubit kedua. Agar ini berfungsi sebagaimana mestinya, penting bahwa Qubit kedua (tujuan) tidak berbagi keterikatan (entanglement) dengan sisa sistem; jika tidak, operasi reset akan menyebabkan state sisa sistem runtuh sebagian.
Di sini, kita membangun Circuit baru dengan satu Qubit tambahan dan operasi Move yang ditempatkan. Dalam contoh ini, kita dapat menggunakan kembali sebuah Qubit: Qubit sumber dari Move pertama menjadi Qubit tujuan dari operasi Move kedua.
Catatan: Sebagai alternatif untuk bekerja langsung dengan instruksi Move, seseorang dapat memilih untuk menandai wire cut menggunakan instruksi CutWire satu-Qubit. Fungsi cut_wires ada untuk mengubah CutWire menjadi instruksi Move pada Qubit yang baru dialokasikan. Namun, berbeda dengan metode manual, metode otomatis ini tidak memungkinkan penggunaan kembali jalur Qubit. Lihat panduan cara penggunaan CutWire untuk detailnya.
from qiskit_addon_cutting.instructions import Move
qc_1 = QuantumCircuit(8)
for i in [*range(4), *range(5, 8)]:
qc_1.rx(np.pi / 4, i)
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.append(Move(), [3, 4])
qc_1.cx(4, 5)
qc_1.cx(4, 6)
qc_1.cx(4, 7)
qc_1.append(Move(), [4, 3])
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.draw("mpl")

Buat observable yang sesuai dengan Circuit baruβ
Observable ini berkorespondensi dengan observable, tetapi kita harus memperhitungkan dengan benar jalur Qubit ekstra yang telah ditambahkan (yaitu, kita menyisipkan "I" pada indeks 4). Perlu diingat bahwa dalam Qiskit, representasi string Qubit-0 berkorespondensi dengan karakter Pauli paling kanan.
observable_expanded = SparsePauliOp(["ZIIIIIII", "IIIIZIII", "IIIIIIIZ"])
Pisahkan Circuit dan observableβ
Seperti dalam tutorial sebelumnya, Qubit yang berbagi label partisi yang sama akan dikelompokkan bersama, dan gate non-lokal yang mencakup lebih dari satu partisi akan dipotong.
from qiskit_addon_cutting import partition_problem
partitioned_problem = partition_problem(
circuit=qc_1, partition_labels="AAAABBBB", observables=observable_expanded.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases
Visualisasikan masalah yang telah didekomposisiβ
subobservables
{'A': PauliList(['IIII', 'ZIII', 'IIIZ']),
'B': PauliList(['ZIII', 'IIII', 'IIII'])}
subcircuits["A"].draw("mpl")

subcircuits["B"].draw("mpl")

Hitung overhead pengambilan sampel untuk potongan yang dipilihβ
Di sini kita memotong dua wire, menghasilkan overhead pengambilan sampel sebesar .
Untuk informasi lebih lanjut tentang overhead pengambilan sampel yang ditimbulkan oleh circuit cutting, lihat materi penjelasan.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 256.0
Hasilkan subeksperimen untuk dijalankan pada Backendβ
generate_cutting_experiments menerima argumen circuits/observables sebagai kamus yang memetakan label partisi Qubit ke subcircuit/subobservables masing-masing.
Untuk mensimulasikan nilai ekspektasi Circuit berukuran penuh, banyak subeksperimen dihasilkan dari distribusi kuasiprobabilitas gabungan gate-gate yang didekomposisi dan kemudian dieksekusi pada satu atau lebih Backend. Jumlah sampel yang diambil dari distribusi dikontrol oleh num_samples, dan satu koefisien gabungan diberikan untuk setiap sampel unik. Untuk informasi lebih lanjut tentang cara koefisien dihitung, lihat materi penjelasan.
from qiskit_addon_cutting import generate_cutting_experiments
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)
Pilih sebuah Backendβ
Di sini kita menggunakan Backend palsu (fake backend), yang akan mengakibatkan Qiskit Runtime berjalan dalam mode lokal (yaitu, pada simulator lokal).
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
Siapkan subeksperimen untuk Backendβ
Kita harus men-transpile Circuit dengan Backend kita sebagai target sebelum mengirimkannya ke Qiskit Runtime.
from qiskit.transpiler import generate_preset_pass_manager
# Transpile the subexperiments to ISA circuits
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()
}
Langkah 3: Eksekusiβ
Jalankan subeksperimen menggunakan primitif Qiskit Runtime Samplerβ
from qiskit_ibm_runtime import SamplerV2, Batch
# 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=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
/home/garrison/Qiskit/qiskit-ibm-runtime/qiskit_ibm_runtime/session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
warnings.warn(
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
Langkah 4: Pasca-pemrosesanβ
Rekonstruksi nilai ekspektasiβ
Rekonstruksi nilai ekspektasi untuk setiap suku observable dan gabungkan untuk merekonstruksi nilai ekspektasi observable asli.
from qiskit_addon_cutting import reconstruct_expectation_values
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)
Bandingkan nilai ekspektasi yang direkonstruksi dengan nilai ekspektasi eksak dari Circuit dan observable asliβ
from qiskit_aer.primitives import EstimatorV2
estimator = EstimatorV2()
exact_expval = estimator.run([(qc_0, observable)]).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: 1.51319069
Exact expectation value: 1.59099026
Error in estimation: -0.07779957
Relative error in estimation: -0.04890009