Operator backpropagation (OBP) para sa pagtatantya ng expectation values
Tantya sa paggamit: 4 na minuto sa isang Heron r3 processor (PAALALA: Ito ay tantya lamang. Maaaring mag-iba ang iyong runtime.)
Mga layunin sa pag-aaral
Pagkatapos ng tutorial na ito, dapat maunawaan ng mga gumagamit ang:
- Paano gamitin ang
qiskit-addon-obpupang mabawasan ang lalim ng quantum circuit sa halaga ng mas maraming circuit execution - Paano gamitin ang
qiskit-addon-utilsupang bumuo ng mga XYZ Hamiltonian at ang kanilang mga time-evolution circuit
Mga kinakailangan
Iminumungkahi naming maging pamilyar ang mga gumagamit sa mga sumusunod na paksa bago gawin ang tutorial na ito:
- Paggamit ng Estimator primitive upang kalkulahin ang mga expectation value ng isang observable
Background
Ang operator backpropagation ay isang teknikang nagsasangkot ng pag-absorb ng mga operasyon mula sa dulo ng isang quantum circuit tungo sa sinusukat na observable, na karaniwang nagpapababa ng lalim ng circuit sa halaga ng karagdagang mga term sa observable. Ang layunin ay mag-backpropagate ng kasing dami ng circuit hangga't maaari nang hindi pinapayagang lumaki nang labis ang observable. Ang isang Qiskit-based na implementasyon ay available sa OBP Qiskit addon. Basahin ang kaukulang dokumentasyon para sa karagdagang impormasyon.
Isaalang-alang ang isang halimbawang circuit kung saan ang isang observable ay susukatiin, kung saan ang ay mga Pauli at ang ay mga coefficient. Itakda natin ang circuit bilang isang unitary na maaaring lohikal na hatiin sa tulad ng ipinapakita sa figure sa ibaba.

Ang operator backpropagation ay nag-aabsorb ng unitary sa observable sa pamamagitan ng pag-evolve nito bilang . Sa madaling salita, ang bahagi ng computation ay ginagawa nang classical sa pamamagitan ng evolusyon ng observable mula sa tungo sa . Ang orihinal na problema ay maaari ngayong muling iformula bilang pagsukat ng observable para sa bagong mas mababang lalim na circuit na ang unitary ay .
Ang unitary ay kinakatawan bilang isang bilang ng mga slice . May maraming paraan para tukuyin ang isang slice. Halimbawa, sa halimbawang circuit sa itaas, ang bawat layer ng at bawat layer ng gates ay maaaring ituring bilang indibidwal na slice. Ang backpropagation ay nagsasangkot ng pagkalkula ng nang classical. Ang bawat slice ay maaaring katawanin bilang , kung saan ang ay isang -qubit Pauli at ang ay isang scalar. Madaling ma-verify na
Sa halimbawa sa itaas, kung , kailangan nating magsagawa ng dalawang quantum circuit, sa halip na isa, upang kalkulahin ang expectation value. Samakatuwid, ang backpropagation ay maaaring magdagdag ng bilang ng mga term sa observable, na nagreresulta sa mas mataas na bilang ng circuit execution. Isang paraan upang payagan ang mas malalim na backpropagation sa circuit, habang pinipigilan ang operator mula sa paglaki nang labis, ay ang mag-truncate ng mga term na may maliliit na coefficient, sa halip na idagdag ang mga ito sa operator. Halimbawa, sa halimbawa sa itaas, maaari tayong pumili na mag-truncate ng term na nagsasangkot ng kung sakaling ang ay sapat na maliit. Ang pag-truncate ng mga term ay maaaring magresulta sa mas kakaunting quantum circuit na isasagawa, ngunit ang paggawa nito ay nagreresulta ng ilang error sa panghuling pagkalkula ng expectation value na proporsyonal sa magnitude ng mga coefficient ng mga na-truncate na term.
Requirements
Bago simulan ang tutorial na ito, siguraduhing mayroon ka ng mga sumusunod na naka-install:
- Qiskit SDK v2.0 o mas bago, na may suporta sa visualization
- Qiskit Runtime v0.22 o mas bago (
pip install qiskit-ibm-runtime) - OBP Qiskit addon 0.3 o mas bago (
pip install qiskit-addon-obp) - Qiskit addon utils 0.3 o mas bago (
pip install qiskit-addon-utils)
Setup
# 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
Small-scale simulator example
Isinasagawa ng tutorial na ito ang isang Qiskit pattern para sa pag-simulate ng quantum dynamics ng isang Heisenberg spin chain gamit ang OBP Qiskit addon. Pansinin na sa isang noiseless simulator, ang expectation value na nakuha nang may at walang backpropagation ay magiging pareho.
Step 1: Map classical inputs to a quantum problem
Map the time-evolution of a quantum Heisenberg model to a quantum experiment
Una, gagamitin natin ang function na generate_xyz_hamiltonian mula sa qiskit-addon-utils upang bumuo ng isang Heisenberg-like Hamiltonian sa isang ibinigay na connectivity graph. Ang graph na ito ay maaaring rustworkx.PyGraph o CouplingMap. Sa sumusunod, gagamitin natin ang isang linear chain na CouplingMap ng 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")
Susunod, bubuo tayo ng isang Pauli operator na nagmomodelo ng Heisenberg XYZ Hamiltonian:
kung saan ang ay ang graph ng coupling map. Para sa tutorial na ito, ginamit natin ang na , ayon sa pagkakasunod, at na , ayon sa pagkakasunod.
# 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])
Mula sa qubit operator, maaari tayong lumikha ng quantum circuit na nagmomodelo ng time evolution nito. Ginamit namin ang generate_time_evolution_circuit na may Lie Trotter decomposition upang buuin ang time evolution circuit.
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=2),
)
circuit.draw("mpl", style="iqp", fold=-1)

Step 2: Optimize problem for quantum hardware execution
Create circuit slices to backpropagate
Ang function na backpropagate ay nagba-backpropagate ng buong circuit slice nang sabay-sabay. Kaya naman, ang pagpili kung paano maghiwa ay maaaring magkaroon ng epekto sa kung gaano kahusay gumagana ang backpropagation para sa isang partikular na problema. Dito, pagsasamahin natin ang mga gate ng parehong uri sa mga slice gamit ang function na slice_by_depth.
Para sa mas detalyadong talakayan tungkol sa circuit slicing, tingnan ang how-to guide na ito ng package na 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.
Constrain how large the operator can grow during backpropagation
Sa panahon ng backpropagation, ang bilang ng mga term sa operator ay karaniwang lalapit sa nang mabilis, kung saan ang ay ang bilang ng mga slice. Kapag ang dalawang term sa operator ay hindi nag-commute nang qubit-wise, kailangan natin ng hiwalay na mga circuit upang makuha ang mga expectation value na tumutugma sa kanila. Halimbawa, kung mayroon tayong dalawang-qubit observable na , dahil ang , ang pagsukat sa isang basis lamang ay sapat upang kalkulahin ang mga expectation value para sa dalawang term na ito. Gayunpaman, ang ay anti-commute sa dalawang term, kaya kailangan natin ng hiwalay na basis measurement upang kalkulahin ang expectation value ng . Sa madaling salita, kailangan natin ng dalawang circuit sa halip na isa upang kalkulahin ang . Habang dumarami ang bilang ng mga term sa operator, may posibilidad na dumarami rin ang kinakailangang bilang ng circuit execution.
Ang laki ng operator ay maaaring limitahan sa pamamagitan ng pagtukoy ng operator_budget kwarg ng function na backpropagate, na tumatanggap ng isang instance ng OperatorBudget.
Upang kontrolin ang dami ng karagdagang resources (bilang ng circuit execution, at samakatuwid ang kinakailangang QPU time) na inilalaan, pinapaghihigpitan natin ang maximum na bilang ng qubit-wise commuting Pauli groups na pinapayagang magkaroon ang backpropagated observable. Dito, tinukoy natin na ang backpropagation ay dapat tumigil kapag ang bilang ng qubit-wise commuting Pauli groups sa operator ay lumampas sa walo.
op_budget = OperatorBudget(max_qwc_groups=8)
Backpropagate slices from the circuit
Una, tinukoy natin ang observable bilang , kung saan ang ay ang bilang ng mga qubit. Mag-backpropagate tayo ng mga slice mula sa time-evolution circuit hanggang sa ang mga term sa observable ay hindi na maaaring pagsamahin sa walong o mas kaunting qubit-wise commuting Pauli groups.
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])
Sa ibaba, makikita mo na nag-backpropagate tayo ng anim na slice, at ang mga term ay pinagsama sa anim at hindi walong grupo. Ito ay nagpapahiwatig na ang pag-backpropagate pa ng isang slice ay magiging sanhi ng paglampas ng bilang ng mga Pauli group sa walong. Maaari nating i-verify na ito ang kaso sa pamamagitan ng pagsusuri sa ibinalik na metadata. Pansinin din na sa bahaging ito, ang circuit transformation ay eksakto. Ibig sabihin, walang mga term ng bagong observable na ang na-truncate. Ang backpropagated circuit at ang backpropagated operator ay nagbibigay ng eksaktong kinalabasan tulad ng orihinal na circuit at operator.
# 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:
Para sa small-scale na halimbawa sa isang simulator, hindi tayo gagamit ng truncation. Ito ay dahil sa kawalan ng ingay, ang circuit na may at walang backpropagation ay nagdudulot ng parehong resulta, at ang truncation ay nagpapalala ng resulta dahil sa dagdag na approximation.
Transpile the circuits into the basis gate set
Ngayon, ini-transpile natin ang parehong orihinal at backpropagated na mga circuit sa basis gate ng backend. Hindi natin kailangang mag-transpile sa aktwal na backend dahil magpapatakbo tayo sa isang simulator para sa maliit na instance.
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)
Step 3: Execute using Qiskit primitives
Una, lumilikha tayo ng dalawang Primitive Unified Blocs (PUB) na tumutugma sa orihinal na circuit at sa backpropagated na circuit. Pagkatapos, pinapatakbo natin ang mga pub sa isang ideal na Estimator upang makuha ang mga expectation value.
pubs = [(isa_circuit, observable), (isa_bp_circuit, bp_obs)]
rng = np.random.default_rng()
estimator = StatevectorEstimator(seed=rng)
job = estimator.run(pubs)
Step 4: Post-process and return result in desired classical format
Ngayon nakukuha natin ang mga expectation value ng orihinal at backpropagated na mga circuit.
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$')
Tulad ng inaasahan, ang dalawang expectation value ay nagkakaayon. Dahil nagpapatakbo tayo sa isang noiseless statevector simulator, ang backpropagation ay isang eksaktong transformation ng circuit-observable pair, kaya ang orihinal at backpropagated na mga workflow ay dapat magbigay ng parehong halaga ng . Ang benepisyo ng backpropagation ay magiging malinaw lamang sa maingay na hardware, kung saan ang mas maikling backpropagated na circuit ay nakakaipon ng mas kaunting error, tulad ng ipinapakita sa large-scale hardware example sa ibaba.
Large-scale hardware example
Kapag nagde-develop ng eksperimento, kapaki-pakinabang na magsimula sa isang maliit na circuit upang mas madaling ma-visualize at ma-simulate. Ngayon, titingnan natin ang operator backpropagation para sa isang 50-qubit na Heisenberg Hamiltonian na may parehong set ng mga halaga para sa mga parameter na at at ang parehong observable na , ngunit para sa apat na Trotter step. Ang ideal na expectation value sa sukatang ito ay hindi maaaring kalkulahin sa pamamagitan ng brute force, kaya gumagamit tayo ng tensor network at nakakakuha ng ideal na expectation value na .
Kasama ang backpropagation, sa large-scale na halimbawa na ito, ipinakilala rin natin ang backpropagation na may truncation. Idealmente, gusto nating mag-backpropagate hangga't maaari upang mabawasan ang lalim ng epektibong circuit. Gayunpaman, madalas itong humahantong sa malaking bilang ng mga non-commuting na term sa na-update na observable, na nagpapataas ng quantum overhead. Kaya naman, maaari tayong mag-eliminate ng mga term sa observable na may maliliit na coefficient gamit ang isang paraan na tinatawag na truncation. Habang pinapahintulutan ng truncation ang mas malalim na propagation sa pamamagitan ng pagbabawas ng bilang ng mga term sa na-update na observable, nagdudulot din ito ng ilang approximation. Samakatuwid, kinakailangang limitahan ang truncation sa loob ng ilang hangganan upang ang approximation error ay hindi mangibabaw sa pagbabawas ng ingay na nakuha mula sa mas malalim na backpropagation.
Upang limitahan ang dami ng truncation, naglalaan tayo ng error budget para sa bawat slice pati na rin ang kabuuang error budget para sa buong backpropagated na circuit gamit ang function na setup_budget. Tinitiyak nito na ang truncation ay kontrolado para sa bawat slice pati na rin para sa buong circuit. Tingnan din ang guide na ito para sa iba pang paraan ng paglalaan ng budget.
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$')
Next steps
Kung nahanap mong kawili-wili ang gawaing ito, maaaring interesado ka sa mga sumusunod na materyal:
- Approximate quantum compilation for time evolution circuits
- Multi-product formulas to reduce Trotter error
pauli-prop, a Rust-accelerated package for Pauli propagation, with tutorials covering OBP, classical expectation-value estimation, and noisy simulation