Wire cutting untuk estimasi nilai ekspektasi
Estimasi penggunaan: satu menit pada prosesor Eagle (CATATAN: Ini hanya perkiraan. Waktu eksekusi kamu mungkin berbeda.)
Latar Belakangβ
Circuit-knitting adalah istilah umum yang mencakup berbagai metode untuk mempartisi sebuah circuit menjadi beberapa subcircuit yang lebih kecil dengan lebih sedikit gate dan/atau Qubit. Setiap subcircuit dapat dieksekusi secara independen dan hasil akhirnya diperoleh melalui proses pasca-pemrosesan klasik atas hasil dari setiap subcircuit. Teknik ini dapat diakses melalui circuit cutting Qiskit addon, penjelasan rinci tentang teknik ini tersedia di docs beserta materi pengantar lainnya.
Notebook ini membahas metode yang disebut wire cutting, di mana circuit dipartisi sepanjang wire [1], [2]. Perlu dicatat bahwa partisi pada circuit klasik cukup sederhana karena keluaran di titik partisi dapat ditentukan secara deterministik, yaitu 0 atau 1. Namun, keadaan Qubit di titik pemotongan umumnya berupa mixed state. Oleh karena itu, setiap subcircuit perlu diukur beberapa kali dalam basis yang berbeda (biasanya set basis yang lengkap secara tomografi seperti basis Pauli [3], [4] dan disiapkan di eigenstate-nya yang sesuai. Gambar di bawah (courtesy: PhD Thesis, Ritajit Majumdar) menunjukkan contoh wire cutting untuk GHZ state 4-Qubit menjadi tiga subcircuit. Di sini menunjukkan sekumpulan basis (biasanya Pauli X, Y, dan Z) dan menunjukkan sekumpulan eigenstate (biasanya , , , dan ).
Karena setiap subcircuit memiliki lebih sedikit Qubit dan/atau Gate, mereka diharapkan lebih tahan terhadap noise. Notebook ini menunjukkan contoh di mana metode ini dapat digunakan untuk secara efektif menekan noise dalam sistem.
Persyaratanβ
Sebelum memulai tutorial ini, pastikan kamu sudah menginstal hal-hal berikut:
- Qiskit SDK v2.0 atau lebih baru, dengan dukungan visualisasi
- Qiskit Runtime v0.22 atau lebih baru (
pip install qiskit-ibm-runtime) - Circuit cutting Qiskit addon v0.9.0 atau lebih baru (
pip install qiskit-addon-cutting)
Kita akan menggunakan circuit Many Body Localization (MBL) untuk notebook ini. Circuit MBL adalah circuit yang efisien secara hardware dan diparameterisasi oleh dua parameter dan . Ketika diset ke dan keadaan awal disiapkan di untuk semua Qubit, nilai ekspektasi ideal dari adalah untuk setiap qubit site terlepas dari nilai-nilai . Kamu bisa melihat detail lebih lanjut tentang circuit MBL di paper ini.
Setupβ
# Added by doQumentation β required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
import numpy as np
import matplotlib.pyplot as plt
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.result import sampled_expectation_value
from qiskit_addon_cutting.instructions import CutWire
from qiskit_addon_cutting import (
cut_wires,
expand_observables,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, Batch
class MBLChainCircuit(QuantumCircuit):
def __init__(
self, num_qubits: int, depth: int, use_cut: bool = False
) -> None:
super().__init__(
num_qubits, name=f"MBLChainCircuit<{num_qubits}, {depth}>"
)
evolution = MBLChainEvolution(num_qubits, depth, use_cut)
self.compose(evolution, inplace=True)
class MBLChainEvolution(QuantumCircuit):
def __init__(self, num_qubits: int, depth: int, use_cut) -> None:
super().__init__(
num_qubits, name=f"MBLChainEvolution<{num_qubits}, {depth}>"
)
theta = Parameter("ΞΈ")
phis = ParameterVector("Ο", num_qubits)
for layer in range(depth):
layer_parity = layer % 2
# print("layer parity", layer_parity)
for qubit in range(layer_parity, num_qubits - 1, 2):
# print(qubit)
self.cz(qubit, qubit + 1)
self.u(theta, 0, np.pi, qubit)
self.u(theta, 0, np.pi, qubit + 1)
if (
use_cut
and layer_parity == 0
and (
qubit == num_qubits // 2 - 1
or qubit == num_qubits // 2
)
):
self.append(CutWire(), [num_qubits // 2])
if use_cut and layer < depth - 1 and layer_parity == 1:
if qubit == num_qubits // 2:
self.append(CutWire(), [qubit])
for qubit in range(num_qubits):
self.p(phis[qubit], qubit)
Bagian I. Contoh skala kecilβ
Langkah 1: Petakan input klasik ke masalah kuantumβ
Pertama kita membangun circuit template tanpa nilai parameter tertentu. Kita juga menyediakan placeholder yang disebut CutWire untuk menandai posisi pemotongan. Untuk contoh skala kecil ini, kita menggunakan circuit MBL 10-Qubit.
num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)
Ingat bahwa kita ingin mencari nilai ekspektasi dari observable ketika . Kita akan memberikan nilai acak untuk parameter .
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
params
[0,
0.2376615174332788,
0.28244289857682414,
0.019248960591717768,
0.46140600996102477,
0.31408025180068433,
0.718184005135733,
0.991153920182475,
0.09289485768301442,
0.8857848280067783,
0.6177529765767047]
Sekarang kita menandai circuit untuk pemotongan dengan menyisipkan CutWire yang tepat untuk membuat dua pemotongan yang kira-kira sama. Kita set use_cut=True dalam fungsi tersebut, dan biarkan fungsi menandai setelah Qubit, dengan adalah jumlah Qubit dalam circuit asli.
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)
Langkah 2: Optimalkan masalah untuk eksekusi hardware kuantumβ
Selanjutnya kita memotong circuit menjadi dua subcircuit yang lebih kecil. Untuk contoh ini, kita hanya membuat 2 subcircuit. Untuk ini, kita menggunakan Qiskit Addon: Circuit Cutting.
Potong circuit menjadi subcircuit yang lebih kecilβ
Memotong wire pada suatu titik akan menambah jumlah Qubit sebesar satu. Selain Qubit asli, sekarang ada Qubit ekstra sebagai placeholder untuk circuit setelah pemotongan. Gambar berikut memberikan representasinya:
Addon ini menggunakan fungsi cut_wires untuk memperhitungkan Qubit ekstra yang muncul akibat pemotongan.
mbl_move = cut_wires(mbl_cut)
Buat dan perluas observableβ
Sekarang kita membangun observable . Karena hasil ideal dari untuk setiap adalah , hasil ideal dari juga .
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
'IIIIIIIIZI', 'IIIIIIIIIZ'])
Namun, perlu dicatat bahwa jumlah Qubit dalam circuit telah bertambah setelah menyisipkan operasi virtual 2-Qubit Move setelah pemotongan. Oleh karena itu, kita perlu memperluas observable dengan menyisipkan identitas agar sesuai dengan circuit saat ini.
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])
Perhatikan bahwa setiap observable kini telah diperluas untuk mengakomodasi tujuh Qubit, seperti pada circuit dengan operasi Move, bukan 6 Qubit aslinya. Selanjutnya, partisi circuit menjadi dua subcircuit.
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
Mari kita visualisasikan subcircuit-nya
subcircuits = partitioned_problem.subcircuits
subcircuits[0].draw("mpl", fold=-1)
subcircuits[1].draw("mpl", fold=-1)
Observable juga telah dipartisi agar sesuai dengan subcircuit
subobservables = partitioned_problem.subobservables
subobservables
{0: PauliList(['IIIIII', 'IIIIII', 'IIIIII', 'IIIIII', 'IIIIII', 'IZIIII',
'IIZIII', 'IIIZII', 'IIIIZI', 'IIIIIZ']),
1: PauliList(['ZIIII', 'IZIII', 'IIZII', 'IIIZI', 'IIIIZ', 'IIIII', 'IIIII',
'IIIII', 'IIIII', 'IIIII'])}
Perhatikan bahwa setiap subcircuit menghasilkan sejumlah sampel. Proses rekonstruksi memperhitungkan hasil dari setiap sampel tersebut. Setiap sampel ini disebut sebagai subexperiment.
Memperluas observable menggunakan operasi Move memerlukan struktur data PauliList. Kita juga bisa membuat observable dalam struktur data SparsePauliOp yang lebih umum, yang akan berguna nantinya saat merekonstruksi subexperiment.
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
M_z
SparsePauliOp(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII', 'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII', 'IIIIIIIIZI', 'IIIIIIIIIZ'],
coeffs=[0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j,
0.1+0.j, 0.1+0.j])
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
Mari kita lihat dua contoh di mana Qubit yang dipotong diukur dalam dua basis berbeda. Pertama, diukur dalam basis Z normal, kemudian diukur dalam basis X.
subexperiments[0][6].draw("mpl", fold=-1)
subexperiments[0][2].draw("mpl", fold=-1)
Transpile setiap subexperimentβ
Saat ini kita perlu melakukan transpilasi circuit sebelum mengirimkannya untuk dieksekusi. Oleh karena itu, kita akan mentranspilasi setiap circuit dalam subexperiment terlebih dahulu.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
Sekarang kita perlu mentranspilasi setiap circuit dalam subexperiment. Untuk itu, pertama kita membuat pass manager, lalu menggunakannya untuk mentranspilasi setiap circuit.
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
isa_subexperiments[0][0].draw("mpl", fold=-1, idle_wires=False)
Langkah 3: Eksekusi menggunakan Qiskit primitivesβ
Sekarang kita akan mengeksekusi setiap Circuit dalam subexperiment. Qiskit-addon-cutting menggunakan SamplerV2 untuk mengeksekusi subexperiment.
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()
}
Langkah 4: Pasca-proses dan kembalikan hasil dalam format klasik yang diinginkanβ
Setelah Circuit dieksekusi, kita perlu mengambil hasilnya dan merekonstruksi nilai ekspektasi untuk Circuit yang tidak dipotong beserta observable aslinya.
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
0.9674376845359803
Verifikasi silangβ
Sekarang mari kita eksekusi Circuit tanpa pemotongan dan periksa hasilnya. Perlu dicatat bahwa untuk eksekusi Circuit yang tidak dipotong, kita bisa langsung menggunakan EstimatorV2 untuk menghitung nilai ekspektasi. Namun kita akan menggunakan Primitive yang sama sepanjang waktu. Jadi kita akan menggunakan SamplerV2 untuk mendapatkan distribusi probabilitas dan menghitung nilai ekspektasi menggunakan fungsi sampled_expectation_value.
Pertama kita perlu mentranspile Circuit mbl yang tidak dipotong.
sampler = SamplerV2(mode=backend)
if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)
Selanjutnya kita konstruksi pub dan jalankan Circuit yang tidak dipotong.
pub = (isa_mbl, params)
uncut_job = sampler.run([pub])
uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)
uncut_expval
0.9498046875000001
Kita mencatat bahwa nilai ekspektasi yang diperoleh melalui wire cutting lebih dekat ke nilai ideal dibandingkan yang tidak dipotong. Sekarang mari kita perbesar skala masalah.
Bagian II. Perbesar skalanya!β
Sebelumnya, kita menunjukkan hasil untuk Circuit MBL 10-Qubit. Selanjutnya, kita tunjukkan bahwa peningkatan nilai ekspektasi juga diperoleh untuk Circuit yang lebih besar. Untuk mendemonstrasikannya, kita ulangi proses untuk Circuit MBL 60-Qubit.
Langkah 1: Petakan masukan klasik ke masalah kuantumβ
num_qubits = 60
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
Kita buat sekumpulan nilai acak untuk
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
Selanjutnya kita konstruksi Circuit yang dipotong
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)
Langkah 2: Optimalkan masalah untuk eksekusi hardware kuantumβ
Seperti yang ditunjukkan pada contoh skala kecil, kita partisi Circuit dan observable untuk eksperimen pemotongan.
mbl_move = cut_wires(mbl_cut)
# Define observable
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)
# Partition the circuit into subcircuits
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
# Get subcircuits
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
Kita juga membuat objek SparsePauliOp untuk observable dengan koefisien yang sesuai.
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
Selanjutnya kita buat subexperiment dan transpile setiap Circuit dalam subexperiment.
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
Langkah 3: Eksekusi menggunakan Qiskit primitivesβ
Kita gunakan mode Batch untuk mengeksekusi semua Circuit dalam subexperiment.
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()
}
Langkah 4: Pasca-proses dan kembalikan hasil dalam format klasik yang diinginkanβ
Sekarang mari kita ambil hasil untuk setiap Circuit dalam subexperiment dan rekonstruksi nilai ekspektasi yang sesuai dengan Circuit yang tidak dipotong dan observable aslinya.
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
0.9631355921427409
Verifikasi silangβ
Seperti pada contoh skala kecil, kita akan sekali lagi mendapatkan nilai ekspektasi dengan mengeksekusi Circuit yang tidak dipotong, dan membandingkan hasilnya dengan circuit cutting. Kita akan menggunakan SamplerV2 untuk menjaga keseragaman dalam penggunaan Primitives.
sampler = SamplerV2(mode=backend)
if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)
pub = (isa_mbl, params)
uncut_job = sampler.run([pub])
uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)
uncut_expval
0.9426757812499998
Visualisasiβ
Mari kita visualisasikan peningkatan yang diperoleh dalam nilai ekspektasi dengan menggunakan wire cutting.
ax = plt.gca()
methods = ["cut", "uncut"]
values = [reconstructed_expval, uncut_expval]
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
ax.set_ylim([0.85, 1.02])
plt.text(0.3, 0.99, "Exact result")
plt.show()
Kesimpulanβ
Kita mengamati bahwa baik pada masalah skala kecil maupun besar, wire cutting menghasilkan hasil yang lebih baik daripada yang tidak dipotong. Perlu dicatat bahwa tidak ada teknik mitigasi error yang digunakan dalam eksperimen ini. Oleh karena itu, peningkatan hasil yang diperoleh semata-mata disebabkan oleh wire cutting. Dimungkinkan untuk lebih meningkatkan hasil menggunakan berbagai metode mitigasi bersama dengan circuit cutting.
Selain itu, dalam notebook ini, kita menghitung kedua subCircuit pada hardware yang sama. Dalam [5], [6], para penulis menunjukkan metode untuk mendistribusikan subCircuit ke hardware yang berbeda menggunakan informasi noise guna memaksimalkan penekanan noise, dan memparalelkan prosesnya.
Lampiran: pertimbangan skala sumber dayaβ
Jumlah Circuit yang harus dieksekusi bertambah seiring dengan jumlah potongan. Oleh karena itu, meski banyak potongan dapat menghasilkan subCircuit yang kecil sehingga lebih meningkatkan performa, hal itu juga menyebabkan jumlah eksekusi Circuit yang sangat tinggi, yang mungkin tidak praktis untuk sebagian besar kasus. Di bawah ini, kita menunjukkan contoh jumlah subCircuit yang berkaitan dengan jumlah potongan untuk Circuit 50-Qubit.
Perlu dicatat bahwa bahkan untuk lima potongan, jumlah subexperiment sekitar 200 ribu. Oleh karena itu, circuit cutting sebaiknya hanya digunakan ketika jumlah potongan kecil.
Satu contoh Circuit yang ramah potong dan tidak ramah potongβ
Circuit yang ramah potongβ
Seperti yang dicatat sebelumnya, sebuah Circuit dikatakan ramah potong ketika Circuit tersebut dapat dipartisi menjadi subCircuit yang lebih kecil dan terpisah dengan jumlah potongan yang sedikit. Circuit apa pun yang efisien secara hardware, yaitu Circuit yang memerlukan sedikit atau tidak ada Gate SWAP saat dipetakan ke coupling map hardware, pada umumnya bersifat ramah potong. Di bawah ini, kita menunjukkan contoh ansatz yang mempertahankan eksitasi, yang digunakan dalam Kimia Kuantum. Perlu dicatat bahwa Circuit semacam itu dapat dipartisi menjadi dua subCircuit dengan satu potongan saja tanpa memandang jumlah Qubit.

Circuit yang tidak ramah potongβ
Sebuah Circuit dikatakan tidak ramah potong jika, pada umumnya, jumlah potongan yang diperlukan untuk membentuk partisi yang terpisah bertambah secara signifikan seiring dengan kedalaman atau jumlah Qubit. Ingat bahwa setiap potongan memerlukan satu Qubit tambahan. Jadi seiring bertambahnya jumlah potongan, jumlah Qubit efektif juga bertambah. Di bawah ini kita menunjukkan contoh Circuit Grover 3-Qubit dengan kemungkinan instance pemotongan.
Kita mencatat bahwa diperlukan tiga potongan, dan potongannya lebih bersifat vertikal daripada horizontal. Ini berarti bahwa jumlah potongan diperkirakan akan bertambah secara linier dengan jumlah Qubit, yang tidak cocok untuk pemotongan.
Referensiβ
[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulating large quantum circuits on a small quantum computer. Physical review letters, 125(15), 150504.
[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: using small quantum computers for large quantum circuit evaluations. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).
[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum circuit cutting with maximum-likelihood tomography. npj Quantum Information, 7(1), 64.
[4] Majumdar, R., & Wood, C. J. (2022). Error mitigated quantum circuit cutting. arXiv preprint arXiv:2211.13431.
[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelizing Quantum-Classical Workloads: Profiling the Impact of Splitting Techniques. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.
[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization. arXiv preprint arXiv:2309.06005.
Survei tutorialβ
Silakan ikuti survei singkat ini untuk memberikan masukan tentang tutorial ini. Wawasanmu akan membantu kami meningkatkan konten dan pengalaman pengguna.
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.