Mulai dengan circuit cutting menggunakan wire cuts
Versi paket
Kode di halaman ini dikembangkan menggunakan persyaratan berikut. Kami menyarankan menggunakan versi ini atau yang lebih baru.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17
qiskit-addon-cutting~=0.10.0
Panduan ini mendemonstrasikan contoh penggunaan wire cuts dengan paket qiskit-addon-cutting. Panduan ini mencakup rekonstruksi nilai ekspektasi dari sebuah Circuit tujuh-Qubit menggunakan wire cutting.
Wire cut direpresentasikan dalam paket ini sebagai instruksi dua-Qubit Move, yang didefinisikan sebagai reset pada Qubit kedua yang menjadi operand instruksi, diikuti oleh swap kedua Qubit. Operasi ini setara dengan mentransfer state Qubit pertama ke Qubit kedua, sambil sekaligus membuang state masuk dari Qubit kedua.
Paket ini dirancang agar konsisten dengan cara kamu harus memperlakukan wire cuts saat bekerja dengan Qubit fisik. Misalnya, sebuah wire cut bisa mengambil state Qubit fisik dan melanjutkannya sebagai Qubit fisik setelah pemotongan. Kamu bisa menganggap "instruction cutting" sebagai kerangka kerja terpadu untuk mempertimbangkan wire cuts dan gate cuts dalam formalisme yang sama (karena wire cut hanyalah instruksi Move yang dipotong). Menggunakan kerangka kerja ini untuk wire cutting juga memungkinkan penggunaan ulang Qubit, yang dijelaskan di bagian tentang pemotongan wire secara manual.
Instruksi satu-Qubit CutWire berfungsi sebagai antarmuka yang lebih abstrak dan sederhana untuk bekerja dengan wire cuts. Instruksi ini memungkinkan kamu menandai di mana dalam Circuit sebuah wire harus dipotong di level tinggi dan membiarkan addon circuit cutting menyisipkan instruksi Move yang sesuai untukmu.
Contoh berikut mendemonstrasikan rekonstruksi nilai ekspektasi setelah wire cutting. Kamu akan membuat Circuit dengan beberapa Gate non-lokal dan mendefinisikan observable untuk diestimasi.
# 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
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
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.instructions import Move, CutWire
from qiskit_addon_cutting import (
partition_problem,
generate_cutting_experiments,
cut_wires,
expand_observables,
reconstruct_expectation_values,
)
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)
# Define observable
observable = SparsePauliOp(["ZIIIIII", "IIIZIII", "IIIIIIZ"])
# Draw circuit
qc_0.draw("mpl")
Memotong wire menggunakan instruksi CutWire tingkat tinggiβ
Selanjutnya, buat wire cuts menggunakan instruksi satu-Qubit CutWire pada Qubit . Setelah subeksperimen siap untuk dieksekusi, gunakan fungsi cut_wires() untuk mengubah CutWire menjadi instruksi Move pada Qubit yang baru dialokasikan.
qc_1 = QuantumCircuit(7)
for i in range(7):
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(CutWire(), [3])
qc_1.cx(3, 4)
qc_1.cx(3, 5)
qc_1.cx(3, 6)
qc_1.append(CutWire(), [3])
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.draw("mpl")
Saat sebuah Circuit diperluas melalui satu atau lebih wire cuts, observable perlu diperbarui untuk memperhitungkan Qubit tambahan yang diperkenalkan. Paket qiskit-addon-cutting memiliki fungsi praktis expand_observables(), yang menerima objek PauliList serta Circuit asli dan yang diperluas sebagai argumen, dan mengembalikan PauliList baru.
PauliList yang dikembalikan ini tidak akan berisi informasi tentang koefisien observable asli, namun ini bisa diabaikan hingga rekonstruksi nilai ekspektasi akhir.
# Transform CutWire instructions to Move instructions
qc_2 = cut_wires(qc_1)
# Expand the observable to match the new circuit size
expanded_observable = expand_observables(observable.paulis, qc_0, qc_2)
print(f"Expanded Observable: {expanded_observable}")
qc_2.draw("mpl")
Expanded Observable: ['ZIIIIIIII', 'IIIZIIIII', 'IIIIIIIIZ']
Mempartisi Circuit dan observableβ
Sekarang masalah bisa dipisahkan menjadi partisi-partisi. Ini dilakukan menggunakan fungsi partition_problem() dengan sekumpulan label partisi opsional untuk menentukan cara memisahkan Circuit. Qubit yang berbagi label partisi yang sama dikelompokkan bersama, dan Gate non-lokal yang mencakup lebih dari satu partisi akan dipotong.
Jika tidak ada label partisi yang diberikan, maka partisi akan ditentukan secara otomatis berdasarkan konektivitas Circuit. Baca bagian berikutnya tentang pemotongan wire secara manual untuk informasi lebih lanjut tentang menyertakan label partisi.
partitioned_problem = partition_problem(
circuit=qc_2,
observables=expanded_observable,
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases
print(f"Subobservables to measure: \n{subobservables}\n")
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
subcircuits[0].draw("mpl")
Subobservables to measure:
{0: PauliList(['IIIII', 'ZIIII', 'IIIIZ']), 1: PauliList(['ZIII', 'IIII', 'IIII'])}
Sampling overhead: 256.0
subcircuits[1].draw("mpl")
Dalam skema partisi ini, kamu telah memotong dua wire, menghasilkan sampling overhead sebesar .
Membuat subeksperimen untuk dieksekusi dan memproses hasilnyaβ
Untuk mengestimasi nilai ekspektasi dari Circuit berukuran penuh, beberapa subeksperimen dibuat dari distribusi quasi-probabilitas bersama dari Gate yang didekomposisi, lalu dieksekusi pada satu (atau lebih) QPU. Metode generate_cutting_experiments melakukan ini dengan menerima argumen untuk kamus subcircuits dan subobservables yang kamu buat di atas, serta jumlah sampel yang diambil dari distribusi.
Argumen num_samples menentukan berapa banyak sampel yang diambil dari distribusi quasi-probabilitas dan menentukan akurasi koefisien yang digunakan untuk rekonstruksi. Meneruskan infinity (np.inf) memastikan semua koefisien dihitung secara tepat. Baca dokumen API tentang pembuatan bobot dan pembuatan eksperimen pemotongan untuk informasi lebih lanjut.
# Generate subexperiments
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=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
Terakhir, nilai ekspektasi dari Circuit ukuran penuh bisa direkonstruksi menggunakan metode reconstruct_expectation_values().
Blok kode di bawah ini merekonstruksi hasilnya dan membandingkannya dengan nilai ekspektasi yang tepat.
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
# Apply the coefficients of the original observable
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)
# Compute the exact expectation value using the `qiskit_aer` package.
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.45965266
Exact expectation value: 1.59099026
Error in estimation: -0.1313376
Relative error in estimation: -0.08255085
Untuk merekonstruksi nilai ekspektasi secara akurat, koefisien dari observable asli (yang berbeda dari output generate_cutting_experiments()) harus diterapkan pada output rekonstruksi, karena informasi ini hilang saat eksperimen pemotongan dibuat atau saat observable diperluas.
Biasanya koefisien ini bisa diterapkan melalui numpy.dot() seperti yang ditunjukkan sebelumnya.
Memotong wire menggunakan instruksi Move tingkat rendahβ
Salah satu keterbatasan menggunakan instruksi CutWire tingkat tinggi adalah bahwa instruksi ini tidak memungkinkan penggunaan ulang Qubit. Jika ini diinginkan untuk eksperimen pemotongan, kamu bisa menempatkan instruksi Move secara manual. Namun, karena instruksi Move membuang state dari Qubit tujuan, penting bahwa Qubit ini tidak berbagi keterikatan dengan sisa sistem. Jika tidak, operasi reset akan menyebabkan state Circuit runtuh sebagian setelah wire cut.
Blok kode di bawah ini melakukan wire cut pada Qubit untuk Circuit contoh yang sama seperti yang ditunjukkan sebelumnya. Perbedaannya di sini adalah kamu bisa menggunakan kembali sebuah Qubit dengan membalik operasi Move di tempat wire cut kedua dibuat (namun, ini tidak selalu memungkinkan dan bergantung pada Circuit yang dipotong).
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)
# Expand observable
observable_expanded = SparsePauliOp(["ZIIIIIII", "IIIIZIII", "IIIIIIIZ"])
qc_1.draw("mpl")
Circuit di atas sekarang bisa dipartisi dan eksperimen pemotongan dibuat. Untuk menentukan secara eksplisit bagaimana Circuit harus dipartisi, kamu bisa menambahkan label partisi ke fungsi partition_problem(). Qubit yang berbagi label partisi yang sama dikelompokkan bersama, dan Gate non-lokal yang mencakup lebih dari satu partisi akan dipotong. Kunci dari kamus output partition_problem() akan sesuai dengan yang ditentukan dalam string label.
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
print(f"Subobservables to measure: \n{subobservables}\n")
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
subcircuits["A"].draw("mpl")
Subobservables to measure:
{'A': PauliList(['IIII', 'ZIII', 'IIIZ']), 'B': PauliList(['ZIII', 'IIII', 'IIII'])}
Sampling overhead: 256.0
subcircuits["B"].draw("mpl")
Sekarang eksperimen pemotongan bisa dibuat dan nilai ekspektasi direkonstruksi dengan cara yang sama seperti bagian sebelumnya.
Langkah selanjutnyaβ
- Baca panduan Mulai dengan circuit cutting menggunakan gate cuts.
- Baca makalah arXiv tentang optimal wire cutting untuk lebih memahami kesetaraan antara wire cutting dan gate cutting.