Lewati ke konten utama

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.
  • 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")

Quantum circuit diagram

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")

Quantum circuit diagram

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")

Quantum circuit diagram

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

Quantum circuit diagram

Hitung overhead pengambilan sampel untuk potongan yang dipilih​

Di sini kita memotong dua wire, menghasilkan overhead pengambilan sampel sebesar 444^4.

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