Lewati ke konten utama

Operator backpropagation (OBP) untuk estimasi nilai ekspektasi

Estimasi penggunaan: 4 menit pada prosesor Heron r3 (CATATAN: Ini hanya estimasi. Waktu berjalan kamu bisa berbeda.)

Hasil pembelajaran

Setelah menyelesaikan tutorial ini, pengguna diharapkan memahami:

  • Cara menggunakan qiskit-addon-obp untuk mengurangi kedalaman Circuit kuantum dengan biaya berupa peningkatan jumlah eksekusi Circuit
  • Cara menggunakan qiskit-addon-utils untuk membangun Hamiltonian XYZ dan Circuit evolusi waktunya

Prasyarat

Kami menyarankan pengguna sudah familiar dengan topik-topik berikut sebelum menjalankan tutorial ini:

  • Menggunakan primitif Estimator untuk menghitung nilai ekspektasi dari suatu observable

Latar Belakang

Operator backpropagation adalah teknik yang melibatkan penyerapan operasi dari akhir Circuit kuantum ke dalam observable yang diukur, sehingga secara umum mengurangi kedalaman Circuit dengan biaya berupa tambahan suku pada observable. Tujuannya adalah untuk membackpropagasi sebanyak mungkin Circuit tanpa membiarkan observable tumbuh terlalu besar. Implementasi berbasis Qiskit tersedia di addon OBP Qiskit. Baca dokumentasi terkait untuk informasi lebih lanjut.

Perhatikan contoh Circuit yang di dalamnya observable O=PcPPO = \sum_P c_P P akan diukur, di mana PP adalah Pauli dan cPc_P adalah koefisien. Mari kita notasikan Circuit tersebut sebagai uniter tunggal UU, yang secara logis dapat dibagi menjadi U=UCUQU = U_C U_Q seperti yang ditunjukkan pada gambar di bawah.

Diagram Circuit yang menunjukkan Uq diikuti oleh Uc

Operator backpropagation menyerap uniter UCU_C ke dalam observable dengan mengevolusinya sebagai O=UCOUC=PcPUCPUCO' = U_C^{\dagger}OU_C = \sum_P c_P U_C^{\dagger}PU_C. Dengan kata lain, sebagian komputasi dilakukan secara klasikal melalui evolusi observable dari OO ke OO'. Masalah semula kini dapat diformulasikan ulang sebagai pengukuran observable OO' untuk Circuit baru yang memiliki kedalaman lebih rendah dengan uniter UQU_Q.

Uniter UCU_C direpresentasikan sebagai sejumlah irisan UC=USUS1...U2U1U_C = U_S U_{S-1}...U_2U_1. Ada beberapa cara untuk mendefinisikan sebuah irisan. Misalnya, pada Circuit contoh di atas, setiap lapisan RzzR_{zz} dan setiap lapisan Gate RxR_x dapat dianggap sebagai irisan individual. Backpropagation melibatkan kalkulasi O=Πs=1SPcPUsPUsO' = \Pi_{s=1}^S \sum_P c_P U_s^{\dagger} P U_s secara klasikal. Setiap irisan UsU_s dapat direpresentasikan sebagai Us=exp(iθsPs2)U_s = exp(\frac{-i\theta_s P_s}{2}), di mana PsP_s adalah Pauli nn-Qubit dan θs\theta_s adalah skalar. Mudah untuk diverifikasi bahwa

UsPUs=Pif [P,Ps]=0,U_s^{\dagger} P U_s = P \qquad \text{if} ~[P,P_s] = 0, UsPUs=cos(θs)P+isin(θs)PsPif {P,Ps}=0U_s^{\dagger} P U_s = \qquad cos(\theta_s)P + i sin(\theta_s)P_sP \qquad \text{if} ~\{P,P_s\} = 0

Pada contoh di atas, jika {P,Ps}=0\{P,P_s\} = 0, maka kita perlu menjalankan dua Circuit kuantum, alih-alih satu, untuk menghitung nilai ekspektasi. Oleh karena itu, backpropagation dapat meningkatkan jumlah suku dalam observable, sehingga berpotensi meningkatkan jumlah eksekusi Circuit. Salah satu cara untuk memungkinkan backpropagation lebih dalam ke Circuit, sambil mencegah operator tumbuh terlalu besar, adalah dengan memangkas suku-suku yang memiliki koefisien kecil, daripada menambahkannya ke operator. Misalnya, pada contoh di atas, seseorang dapat memilih untuk memangkas suku yang melibatkan PsPP_sP selama θs\theta_s cukup kecil. Memangkas suku dapat menghasilkan lebih sedikit Circuit kuantum yang perlu dieksekusi, namun hal ini menghasilkan beberapa kesalahan dalam kalkulasi nilai ekspektasi akhir yang sebanding dengan besarnya koefisien suku-suku yang dipangkas.

Persyaratan

Sebelum memulai tutorial ini, pastikan kamu telah 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)
  • OBP Qiskit addon 0.3 atau lebih baru (pip install qiskit-addon-obp)
  • Qiskit addon utils 0.3 atau lebih baru (pip install qiskit-addon-utils)

Pengaturan

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-obp qiskit-addon-utils qiskit-ibm-runtime rustworkx
import numpy as np
import matplotlib.pyplot as plt

from qiskit.primitives import StatevectorEstimator
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import CouplingMap
from qiskit.synthesis import LieTrotter

from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit_addon_utils.slicing import slice_by_depth, combine_slices
from qiskit_addon_obp.utils.simplify import OperatorBudget
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.truncating import setup_budget

from rustworkx.visualization import graphviz_draw

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2, EstimatorOptions

Contoh simulator skala kecil

Tutorial ini mengimplementasikan pola Qiskit untuk mensimulasikan dinamika kuantum rantai spin Heisenberg menggunakan addon OBP Qiskit. Perlu dicatat bahwa pada simulator tanpa derau, nilai ekspektasi yang diperoleh dengan dan tanpa backpropagation akan sama.

Langkah 1: Petakan input klasikal ke masalah kuantum

Petakan evolusi waktu model kuantum Heisenberg ke eksperimen kuantum

Pertama, kita akan menggunakan fungsi generate_xyz_hamiltonian dari qiskit-addon-utils untuk menghasilkan Hamiltonian seperti Heisenberg pada graf konektivitas tertentu. Graf ini bisa berupa rustworkx.PyGraph atau CouplingMap. Berikut ini, kita akan menggunakan CouplingMap rantai linier dari 10 qubit.

num_qubits = 10
layout = [(i - 1, i) for i in range(1, num_qubits)]

# Instantiate a CouplingMap object
coupling_map = CouplingMap(layout)
graphviz_draw(coupling_map.graph, method="circo")

Output of the previous code cell

Selanjutnya, kita membuat operator Pauli yang memodelkan Hamiltonian Heisenberg XYZ:

H^XYZ=(j,k)E(Jxσjxσkx+Jyσjyσky+Jzσjzσkz)+jV(hxσjx+hyσjy+hzσjz),{\hat{\mathcal{H}}_{XYZ} = \sum_{(j,k)\in E} (J_{x} \sigma_j^{x} \sigma_{k}^{x} + J_{y} \sigma_j^{y} \sigma_{k}^{y} + J_{z} \sigma_j^{z} \sigma_{k}^{z}) + \sum_{j\in V} (h_{x} \sigma_j^{x} + h_{y} \sigma_j^{y} + h_{z} \sigma_j^{z}),}

di mana G(V,E)G(V,E) adalah graf dari coupling map. Untuk tutorial ini, kita menggunakan Jx,Jy,JzJ_x, J_y, J_z masing-masing sebesar π8,π4,π2\frac{\pi}{8}, \frac{\pi}{4}, \frac{\pi}{2}, dan hx,hy,hzh_x, h_y, h_z masing-masing sebesar π3,π6,π9\frac{\pi}{3}, \frac{\pi}{6}, \frac{\pi}{9}.

# Get a qubit operator describing the Heisenberg XYZ model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),
ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),
)
print(hamiltonian)
SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII', 'IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIYII', 'IIIIIIIZII', 'IIIIIIXIII', 'IIIIIIYIII', 'IIIIIIZIII', 'IIIIIXIIII', 'IIIIIYIIII', 'IIIIIZIIII', 'IIIIXIIIII', 'IIIIYIIIII', 'IIIIZIIIII', 'IIIXIIIIII', 'IIIYIIIIII', 'IIIZIIIIII', 'IIXIIIIIII', 'IIYIIIIIII', 'IIZIIIIIII', 'IXIIIIIIII', 'IYIIIIIIII', 'IZIIIIIIII', 'XIIIIIIIII', 'YIIIIIIIII', 'ZIIIIIIIII'],
coeffs=[0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,
0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,
1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,
0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,
0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,
1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,
0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,
1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,
1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,
0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,
0.34906585+0.j])

Dari operator qubit, kita bisa membuat Circuit kuantum yang memodelkan evolusi waktunya. Kita menggunakan generate_time_evolution_circuit dengan dekomposisi Lie Trotter untuk membangun Circuit evolusi waktu.

circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=2),
)
circuit.draw("mpl", style="iqp", fold=-1)

Output of the previous code cell

Langkah 2: Optimalkan masalah untuk eksekusi hardware kuantum

Buat irisan Circuit untuk dibackpropagasi

Fungsi backpropagate membackpropagasi seluruh irisan Circuit sekaligus. Oleh karena itu, pilihan cara memotong dapat berdampak pada seberapa baik backpropagation bekerja untuk masalah tertentu. Di sini, kita akan mengelompokkan Gate dengan tipe yang sama ke dalam irisan menggunakan fungsi slice_by_depth.

Untuk diskusi lebih detail tentang pemotongan Circuit, cek panduan how-to dari paket qiskit-addon-utils.

slices = slice_by_depth(circuit, max_slice_depth=1)
print(f"Separated the circuit into {len(slices)} slices.")
Separated the circuit into 18 slices.

Batasi seberapa besar operator dapat tumbuh selama backpropagation

Selama backpropagation, jumlah suku dalam operator secara umum akan mendekati 2L2^L dengan cepat, di mana LL adalah jumlah irisan. Ketika dua suku dalam operator tidak berkomutasi secara qubit-wise, kita memerlukan Circuit terpisah untuk mendapatkan nilai ekspektasi yang sesuai dengan keduanya. Misalnya, jika kita memiliki observable 2-qubit O=0.1XX+0.3IZ0.5IXO = 0.1 XX + 0.3 IZ - 0.5 IX, maka karena [XX,IX]=0[XX,IX] = 0, pengukuran dalam satu basis sudah cukup untuk menghitung nilai ekspektasi kedua suku ini. Namun, IZIZ anti-berkomutasi dengan dua suku lainnya, sehingga kita memerlukan pengukuran basis terpisah untuk menghitung nilai ekspektasi IZIZ. Dengan kata lain, kita memerlukan dua Circuit, bukan satu, untuk menghitung O\langle O \rangle. Seiring bertambahnya jumlah suku dalam operator, ada kemungkinan jumlah eksekusi Circuit yang diperlukan juga meningkat.

Ukuran operator dapat dibatasi dengan menentukan argumen kata kunci operator_budget dari fungsi backpropagate, yang menerima instance OperatorBudget.

Untuk mengontrol jumlah sumber daya tambahan (jumlah eksekusi Circuit, dan dengan demikian waktu QPU yang dibutuhkan) yang dialokasikan, kita membatasi jumlah maksimum grup Pauli yang berkomutasi secara qubit-wise yang boleh dimiliki observable yang telah dibackpropagasi. Di sini kita tentukan bahwa backpropagation harus berhenti ketika jumlah grup Pauli yang berkomutasi secara qubit-wise dalam operator melebihi delapan.

op_budget = OperatorBudget(max_qwc_groups=8)

Backpropagasi irisan dari Circuit

Pertama kita tentukan observable sebagai MZ=1Ni=1NZiM_Z = \frac{1}{N} \sum_{i=1}^N \langle Z_i \rangle, di mana NN adalah jumlah qubit. Kita akan membackpropagasi irisan dari Circuit evolusi waktu hingga suku-suku dalam observable tidak lagi dapat digabungkan menjadi delapan grup Pauli yang berkomutasi secara qubit-wise atau lebih sedikit.

observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / num_qubits) for i in range(num_qubits)],
num_qubits=num_qubits,
)
observable
SparsePauliOp(['IIIIIIIIIZ', 'IIIIIIIIZI', 'IIIIIIIZII', 'IIIIIIZIII', 'IIIIIZIIII', 'IIIIZIIIII', 'IIIZIIIIII', 'IIZIIIIIII', 'IZIIIIIIII', 'ZIIIIIIIII'],
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])

Di bawah ini kamu akan melihat bahwa kita telah membackpropagasi enam irisan, dan suku-sukunya digabungkan menjadi enam, bukan delapan grup. Ini berarti membackpropagasi satu irisan lagi akan menyebabkan jumlah grup Pauli melebihi delapan. Kita dapat memverifikasi bahwa hal ini memang terjadi dengan memeriksa metadata yang dikembalikan. Perlu dicatat juga bahwa pada bagian ini transformasi Circuit bersifat eksak. Artinya, tidak ada suku dari observable baru OO' yang dipangkas. Circuit yang telah dibackpropagasi dan operator yang telah dibackpropagasi memberikan hasil yang sama persis dengan Circuit dan operator aslinya.

# Backpropagate slices onto the observable
bp_obs, remaining_slices, metadata = backpropagate(
observable, slices, operator_budget=op_budget
)
# Recombine the slices remaining after backpropagation
bp_circuit = combine_slices(remaining_slices)

print(f"Backpropagated {metadata.num_backpropagated_slices} slices.")
print(
f"New observable has {len(bp_obs.paulis)} terms, which can be combined into "
f"{len(bp_obs.group_commuting(qubit_wise=True))} groups."
)
print(
f"Note that backpropagating one more slice would result in "
f"{metadata.backpropagation_history[-1].num_paulis[0]} terms "
f"across {metadata.backpropagation_history[-1].num_qwc_groups} groups."
)
print("The remaining circuit after backpropagation looks as follows:")
bp_circuit.draw("mpl", fold=-1, scale=0.6)
Backpropagated 6 slices.
New observable has 60 terms, which can be combined into 6 groups.
Note that backpropagating one more slice would result in 114 terms across 12 groups.
The remaining circuit after backpropagation looks as follows:

Output of the previous code cell

Untuk contoh skala kecil pada simulator, kita tidak akan menggunakan pemangkasan. Hal ini karena tanpa adanya derau, Circuit dengan dan tanpa backpropagation menghasilkan hasil yang sama, dan pemangkasan justru memperburuk hasil karena adanya aproksimasi tambahan.

Transpile Circuit ke basis gate set

Sekarang kita men-transpile Circuit asli dan Circuit yang telah dibackpropagasi ke basis gate backend. Kita tidak perlu men-transpile ke backend asli karena kita akan menjalankan di simulator untuk instance skala kecil ini.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)
<IBMBackend('ibm_kingston')>
pm_basis = generate_preset_pass_manager(
optimization_level=3, basis_gates=backend.configuration().basis_gates
)
isa_circuit = pm_basis.run(circuit)
isa_bp_circuit = pm_basis.run(bp_circuit)

Langkah 3: Eksekusi menggunakan primitif Qiskit

Pertama, kita membuat dua Primitive Unified Bloc (PUB) yang sesuai dengan Circuit asli dan Circuit yang telah dibackpropagasi. Kemudian kita menjalankan pub tersebut pada Estimator ideal untuk mendapatkan nilai ekspektasi.

pubs = [(isa_circuit, observable), (isa_bp_circuit, bp_obs)]
rng = np.random.default_rng()
estimator = StatevectorEstimator(seed=rng)
job = estimator.run(pubs)

Langkah 4: Pasca-proses dan kembalikan hasil ke format klasikal yang diinginkan

Sekarang kita mendapatkan nilai ekspektasi dari Circuit asli dan Circuit yang telah dibackpropagasi.

primitive_result = job.result()
circuit_expval = primitive_result[0].data.evs.item()
bp_circuit_expval = primitive_result[1].data.evs.item()
methods = [
"No backpropagation",
"Backpropagation",
]
values = [circuit_expval, bp_circuit_expval]

ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylim([0.6, 0.92])
ax.set_ylabel(r"$M_Z$", fontsize=12)
Text(0, 0.5, '$M_Z$')

Output of the previous code cell

Seperti yang diharapkan, kedua nilai ekspektasi tersebut sepakat. Karena kita menjalankan di simulator statevector tanpa derau, backpropagation adalah transformasi eksak dari pasangan Circuit-observable, sehingga alur kerja asli dan yang telah dibackpropagasi harus menghasilkan nilai MZM_Z yang sama. Manfaat backpropagation baru terlihat pada hardware yang berisik, di mana Circuit yang lebih pendek setelah backpropagasi mengakumulasi lebih sedikit kesalahan, sebagaimana diilustrasikan pada contoh hardware skala besar di bawah ini.

Contoh hardware skala besar

Saat mengembangkan eksperimen, berguna untuk memulai dengan Circuit kecil agar visualisasi dan simulasi lebih mudah. Sekarang kita melihat operator backpropagation untuk Hamiltonian Heisenberg 50-qubit dengan nilai yang sama untuk parameter JJ dan hh serta observable MZM_Z yang sama, tetapi untuk empat langkah Trotter. Nilai ekspektasi ideal pada skala ini tidak dapat dihitung dengan metode brute force, sehingga kita menggunakan tensor network dan memperoleh nilai ekspektasi ideal sebesar 0.89\simeq 0.89.

Selain backpropagation, pada contoh skala besar ini kita juga memperkenalkan backpropagation dengan pemangkasan. Idealnya kita ingin membackpropagasi sebanyak mungkin untuk mengurangi kedalaman Circuit efektif. Namun, hal ini sering kali menyebabkan banyaknya suku yang tidak berkomutasi dalam observable yang diperbarui, sehingga meningkatkan overhead kuantum. Oleh karena itu, kita dapat menghilangkan suku-suku observable dengan koefisien kecil menggunakan praktik yang disebut pemangkasan. Sementara pemangkasan memungkinkan propagasi lebih banyak dengan mengurangi jumlah suku dalam observable yang diperbarui, pemangkasan juga memperkenalkan beberapa aproksimasi. Oleh karena itu, perlu membatasi pemangkasan dalam batas tertentu agar kesalahan aproksimasi tidak mengalahkan pengurangan derau yang diperoleh dari backpropagation yang lebih dalam.

Untuk membatasi jumlah pemangkasan, kita mengalokasikan anggaran kesalahan untuk setiap irisan sekaligus total anggaran kesalahan untuk seluruh Circuit yang telah dibackpropagasi menggunakan fungsi setup_budget. Ini memastikan bahwa pemangkasan terkontrol untuk setiap irisan maupun untuk keseluruhan Circuit. Lihat juga panduan ini untuk cara lain mengalokasikan anggaran.

num_qubits = 50
layout = [(i - 1, i) for i in range(1, num_qubits)]

# Instantiate a CouplingMap object
coupling_map = CouplingMap(layout)

hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),
ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),
)

# Generate a time evolution circuit for the Hamiltonian
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=4),
)

# Define the observable to measure
observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / num_qubits) for i in range(num_qubits)],
num_qubits,
)

slices = slice_by_depth(circuit, max_slice_depth=1)

# Define the maximum number of qwc groups allowed in the
# backpropagated observable,
# and the truncation error budget
op_budget = OperatorBudget(max_qwc_groups=15)
truncation_error_budget = setup_budget(
max_error_total=0.03, max_error_per_slice=0.005
)

# First backpropagation without truncation
bp_obs, remaining_slices, metadata = backpropagate(
observable, slices, operator_budget=op_budget
)
bp_circuit = combine_slices(remaining_slices)

# Now backpropagate with truncation, using the same operator budget and
# the defined truncation error budget
bp_obs_trunc, remaining_slices_trunc, metadata = backpropagate(
observable,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
bp_circuit_trunc = combine_slices(
remaining_slices_trunc, include_barriers=False
)

# Now we transpile the original circuit and the two backpropagated circuits,
# and apply the layout to the corresponding observables
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)

isa_circuit = pm.run(circuit)
isa_bp_circuit = pm.run(bp_circuit)
isa_bp_circuit_trunc = pm.run(bp_circuit_trunc)

isa_observable = observable.apply_layout(isa_circuit.layout)
isa_bp_observable = bp_obs.apply_layout(isa_bp_circuit.layout)
isa_bp_observable_trunc = bp_obs_trunc.apply_layout(
isa_bp_circuit_trunc.layout
)

# Compare the 2-qubit depth of each transpiled circuit to see how much
# depth backpropagation saved
print(
f"2-qubit depth without backpropagation: "
f"{isa_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"2-qubit depth with backpropagation: "
f"{isa_bp_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"2-qubit depth with backpropagation and truncation: "
f"{isa_bp_circuit_trunc.depth(lambda x: x.operation.num_qubits == 2)}"
)

pubs = [
(isa_circuit, isa_observable),
(isa_bp_circuit, isa_bp_observable),
(isa_bp_circuit_trunc, isa_bp_observable_trunc),
]

# Now we instantiate the Estimator primitive for the hardware with
# ZNE and measurement error
# mitigation and compute the three circuits and observables
options = EstimatorOptions()
options.default_precision = 0.01
options.resilience_level = 2
options.resilience.zne.noise_factors = [1, 1.2, 1.4]
options.resilience.zne.extrapolator = ["linear"]
estimator = EstimatorV2(mode=backend, options=options)

estimator.options.environment.job_tags = ["TUT_OBP"]
job = estimator.run(pubs)

# Retrieve the results and the standard deviations
result_no_bp = job.result()[0].data.evs.item()
result_bp = job.result()[1].data.evs.item()
result_bp_trunc = job.result()[2].data.evs.item()

std_no_bp = job.result()[0].data.stds.item()
std_bp = job.result()[1].data.stds.item()
std_bp_trunc = job.result()[2].data.stds.item()
2-qubit depth without backpropagation: 24
2-qubit depth with backpropagation: 20
2-qubit depth with backpropagation and truncation: 18
print(f"Expectation value without backpropagation: {result_no_bp}")
print(f"Backpropagated expectation value: {result_bp}")
print(f"Backpropagated expectation value with truncation: {result_bp_trunc}")
Expectation value without backpropagation: 0.9543907942381811
Backpropagated expectation value: 0.9445337385406468
Backpropagated expectation value with truncation: 0.934050286970965
# Plot the results
methods = [
"No backpropagation",
"Backpropagation",
"Backpropagation w/ truncation",
]
values = [result_no_bp, result_bp, result_bp_trunc]
error_bars = [std_no_bp, std_bp, std_bp_trunc]

ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.errorbar(methods, values, yerr=error_bars, fmt="o", color="r", capsize=5)
plt.axhline(0.89)
ax.set_ylim([0.8, 0.98])
plt.text(0.25, 0.895, "Exact result")
ax.set_ylabel(r"$M_Z$", fontsize=12)
Text(0, 0.5, '$M_Z$')

Output of the previous code cell

Langkah selanjutnya

Jika kamu menemukan karya ini menarik, kamu mungkin tertarik dengan materi berikut:

Rekomendasi