ابدأ مع OBP
Package versions
The code on this page was developed using the following requirements. We recommend using these versions or newer.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-addon-utils~=0.3.0
qiskit-addon-obp~=0.3.0
عندما تُعدّ حِملًا كميًا باستخدام الانتشار الخلفي للمؤثرات (OBP)، عليك أولًا اختيار "شرائح الدائرة"، ثانيًا تحديد عتبة اقتطاع أو "ميزانية خطأ" لإزالة الحدود ذات المعاملات الصغيرة في المؤثر المنتشر خلفيًا وضبط حدٍّ أعلى للحجم الكلي لهذا المؤثر. أثناء الانتشار الخلفي، سيقترب عدد الحدود في مؤثر دائرة مكوّنة من كيوبت من بسرعة في السيناريو الأسوأ. يشرح هذا الدليل خطوات تطبيق OBP على حِمل كمي.
المكوّن الرئيسي في حزمة qiskit-addons-obp هو دالة backpropagate(). تستقبل هذه الدالة وسيطات تمثّل الرصدية النهائية المراد إعادة بنائها، ومجموعةً من شرائح الدائرة لحسابها كلاسيكيًا، واختياريًا TruncationErrorBudget أو OperatorBudget لتوفير قيود على عملية الاقتطاع. بمجرد تحديد هذه العناصر، يُحسَب المؤثر المنتشر خلفيًا بصورة تكرارية عبر تطبيق البوابات من كل شريحة على النحو التالي:
حيث هو العدد الكلي للشرائح و تمثّل شريحةً واحدة من الدائرة. يستخدم هذا المثال حزمة qiskit-addons-utils لتحضير شرائح الدائرة وتوليد دائرة المثال.
للبدء، تأمّل التطور الزمني لسلسلة Heisenberg XYZ. يأخذ هذا الهاملتوني الشكل
وستكون القيمة المتوقعة المقاسة هي .
يولّد مقتطف الكود التالي الهاملتوني على شكل SparsePauliOp باستخدام وحدة qiskit_addons_utils.problem_generators وخريطة CouplingMap. اضبط ثوابت الاقتران على ، ، والحقول المغناطيسية الخارجية على ، ، ، ثم ولّد دائرة تُنمذج تطوّرها الزمني.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-obp qiskit-addon-utils qiskit-ibm-runtime
import numpy as np
from qiskit.transpiler import CouplingMap
from qiskit.synthesis import LieTrotter
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2
from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2
from qiskit.primitives import StatevectorEstimator
from qiskit.quantum_info import SparsePauliOp
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import slice_by_gate_types
from qiskit_addon_obp.utils.simplify import OperatorBudget
from qiskit_addon_obp.utils.truncating import setup_budget
from qiskit_addon_obp import backpropagate
from qiskit_addon_utils.slicing import combine_slices
coupling_map = CouplingMap.from_heavy_hex(3, bidirectional=False)
# Choose a 10-qubit linear chain on this coupling map
reduced_coupling_map = coupling_map.reduce(
[0, 13, 1, 14, 10, 16, 5, 12, 8, 18]
)
# Get a qubit operator describing the Heisenberg XYZ model
hamiltonian = generate_xyz_hamiltonian(
reduced_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),
)
# we evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=2), time=0.2
)
circuit.draw("mpl")
تحضير المدخلات للانتشار الخلفي
بعد ذلك، ولّد شرائح الدائرة للانتشار الخلفي. بشكل عام، يمكن أن تؤثر طريقة التقطيع على مدى فعالية الانتشار الخلفي لمسألة معينة. هنا، نجمع البوابات من النوع ذاته في شرائح باستخدام دالة qiskit_addons_utils.slice_by_gate_types.
slices = slice_by_gate_types(circuit)
print(f"Separated the circuit into {len(slices)} slices.")
Separated the circuit into 18 slices.
بعد توليد الشرائح، حدّد OperatorBudget لتزويد دالة backpropagate() بشرط يوقف الانتشار الخلفي للمؤثر ويمنع تنامي التكلفة الكلاسيكية. يمكنك أيضًا تحديد ميزانية خطأ اقت طاع لكل شريحة، حيث ستُقتطع حدود Pauli ذات المعاملات الصغيرة من كل شريحة حتى تمتلئ الميزانية، وأي ميزانية متبقية ستُضاف إلى ميزانية الشريحة التالية.
هنا، نحدد أن الانتشار الخلفي يجب أن يتوقف عندما يتجاوز عدد مجموعات Pauli المتبادلة التبديل كيوبتيًا في المؤثر ، ونخصص ميزانية خطأ قدرها لكل شريحة.
op_budget = OperatorBudget(max_qwc_groups=8)
truncation_error_budget = setup_budget(max_error_per_slice=0.005)
الانتشار الخلفي للشرائح
في هذه الخطوة ستحدد الرصدية النهائية المراد قياسها وتُشغّل الانتشار الخلفي عبر كل شريحة. تُعيد دالة backpropagate() ثلاثة مخرجات: الرصدية المنتشرة خلفيًا، وشرائح الدائرة المتبقية التي لم يُطبَّق عليها الانتشار الخلفي (والتي يجب تشغيلها على العتاد الكمي)، وبيانات وصفية عن عملية الانتشار الخلفي.
لاحظ أن كلًا من OperatorBudget وTruncationErrorBudget وسيطان اختياري ان لدالة backpropagate(). بشكل عام، يجب اختيار القيمة المثلى لكليهما بشكل تجريبي وتتطلب بعض التجريب. في هذا المثال سننفذ الانتشار الخلفي مع TruncationErrorBudget وبدونه.
بشكل افتراضي، تستخدم backpropagate() معيار للمعاملات المقتطعة لتحديد الحد الأعلى للخطأ الكلي الناتج عن الاقتطاع، لكن يمكن استخدام معايير أخرى إذا أردت تعديل طريقة حساب خطأ الاقتطاع.
# Specify a single-qubit observable
observable = SparsePauliOp("IIIIIIIIIZ")
# Backpropagate without the truncation error budget
backpropagated_observable, remaining_slices, metadata = backpropagate(
observable,
slices,
operator_budget=op_budget,
)
# Recombine the slices remaining after backpropagation
bp_circuit = combine_slices(remaining_slices, include_barriers=True)
print(f"Backpropagated {metadata.num_backpropagated_slices} slices.")
print(
f"New observable has {len(backpropagated_observable.paulis)} terms, which can be combined into "
f"{len(backpropagated_observable.group_commuting(qubit_wise=True))} groups.\n"
f"After truncation, the error in our observable is bounded by {metadata.accumulated_error(0):.3e}"
)
print(
f"Note that backpropagating one more slice would result in {metadata.backpropagation_history[-1].num_paulis[0]} terms "
f"across {metadata.backpropagation_history[-1].num_qwc_groups} groups."
)
Backpropagated 7 slices.
New observable has 18 terms, which can be combined into 8 groups.
After truncation, the error in our observable is bounded by 0.000e+00
Note that backpropagating one more slice would result in 27 terms across 12 groups.
print(
"The remaining circuit after backpropagation without truncation looks as follows:"
)
bp_circuit.draw("mpl", scale=0.6)
The remaining circuit after backpropagation without truncation looks as follows:
يُنفّذ مقتطف الكود التالي الانتشار الخلفي للدائرة مع ميزانية خطأ الاقتطاع.
# Backpropagate *with* the truncation error budget
backpropagated_observable_trunc, remaining_slices_trunc, metadata_trunc = (
backpropagate(
observable,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
)
# Recombine the slices remaining after backpropagation
bp_circuit_trunc = combine_slices(
remaining_slices_trunc, include_barriers=True
)
print(f"Backpropagated {metadata_trunc.num_backpropagated_slices} slices.")
print(
f"New observable has {len(backpropagated_observable_trunc.paulis)} terms, which can be combined into "
f"{len(backpropagated_observable_trunc.group_commuting(qubit_wise=True))} groups.\n"
f"After truncation, the error in our observable is bounded by {metadata_trunc.accumulated_error(0):.3e}"
)
print(
f"Note that backpropagating one more slice would result in {metadata_trunc.backpropagation_history[-1].num_paulis[0]} terms "
f"across {metadata_trunc.backpropagation_history[-1].num_qwc_groups} groups."
)
Backpropagated 10 slices.
New observable has 19 terms, which can be combined into 8 groups.
After truncation, the error in our observable is bounded by 4.933e-02
Note that backpropagating one more slice would result in 27 terms across 13 groups.
print(
"The remaining circuit after backpropagation with truncation looks as follows:"
)
bp_circuit_trunc.draw("mpl", scale=0.6)
The remaining circuit after backpropagation with truncation looks as follows:
تحويل التمثيل وتنفيذ الحِمل الكمي
الآن بعد أن أجريت الانتشار الخلفي للمؤثر، يمكنك تنفيذ الجزء المتبقي من الدائرة على وحدة معالجة كمومية (QPU). يجب أن يتضمن الحِمل الكمي باستخدام Estimator دائرة bp_circuit_trunc وقياس المؤثر المنتشر خلفيًا backpropagated_observable.
لإثبات فعالية OBP بشكل منفرد، يُحوّل مقتطف الكود التالي كلًا من الدائرة الأصلية والدائرة المنتشرة خلفيًا (مع الاقتطاع وبدونه) ويُحاكي الدارتين كلاسيكيًا باستخدام StatevectorEstimator.
# Specify a backend and a pass manager for transpilation
backend = FakeMelbourneV2()
# pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
# Transpile original experiment
circuit_isa = pm.run(circuit)
observable_isa = observable.apply_layout(circuit_isa.layout)
# Transpile backpropagated experiment without truncation
bp_circuit_isa = pm.run(bp_circuit)
bp_obs_isa = backpropagated_observable.apply_layout(bp_circuit_isa.layout)
# Transpile the backpropagated experiment with truncated observable terms
bp_circuit_trunc_isa = pm.run(bp_circuit_trunc)
bp_obs_trunc_isa = backpropagated_observable_trunc.apply_layout(
bp_circuit_trunc_isa.layout
)
estimator = StatevectorEstimator()
# Run the experiments using the exact statevector estimator
result_exact = (
estimator.run([(circuit, observable)]).result()[0].data.evs.item()
)
result_bp = (
estimator.run([(bp_circuit_isa, bp_obs_isa)]).result()[0].data.evs.item()
)
result_bp_trunc = (
estimator.run([(bp_circuit_trunc_isa, bp_obs_trunc_isa)])
.result()[0]
.data.evs.item()
)
print(f"Exact expectation value: {result_exact}")
print(f"Backpropagated expectation value without truncation: {result_bp}")
print(f"Backpropagated expectation value with truncation: {result_bp_trunc}")
print(
f" - Expected Error for truncated observable: {metadata_trunc.accumulated_error(0):.3e}"
)
print(
f" - Observed Error for truncated observable: {abs(result_exact - result_bp_trunc):.3e}"
)
Exact expectation value: 0.8854160687717517
Backpropagated expectation value without truncation: 0.8854160687717533
Backpropagated expectation value with truncation: 0.8850236647156081
- Expected Error for truncated observable: 4.933e-02
- Observed Error for truncated observable: 3.924e-04
وأخيرًا، يُحوّل مقتطف الكود التالي الدائرة المنتشرة خلفيًا وينفّذها على وحدة QPU (مع الاقتطاع وبدونه).
# Specify a backend and a pass manager for transpilation
service = QiskitRuntimeService()
backend = service.least_busy()
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
# Transpile backpropagated experiment without truncation
bp_circuit_isa = pm.run(bp_circuit)
bp_obs_isa = backpropagated_observable.apply_layout(bp_circuit_isa.layout)
# Transpile the backpropagated experiment with truncated observable terms
bp_circuit_trunc_isa = pm.run(bp_circuit_trunc)
bp_obs_trunc_isa = backpropagated_observable_trunc.apply_layout(
bp_circuit_trunc_isa.layout
)
# Run the experiments using Estimator primitive
estimator = EstimatorV2(mode=backend)
result_bp_qpu = (
estimator.run([(bp_circuit_isa, bp_obs_isa)]).result()[0].data.evs.item()
)
result_bp_trunc_qpu = (
estimator.run([(bp_circuit_trunc_isa, bp_obs_trunc_isa)])
.result()[0]
.data.evs.item()
)
print(f"Exact expectation value: {result_exact}")
print(f"Backpropagated expectation value without truncation: {result_bp_qpu}")
print(
f"Backpropagated expectation value with truncation: {result_bp_trunc_qpu}"
)
print(
f" - Observed Error for observable without truncation: {abs(result_exact - result_bp_qpu):.3e}"
)
print(
f" - Observed Error for truncated observable: {abs(result_exact - result_bp_trunc_qpu):.3e}"
)
Exact expectation value: 0.8854160687717517
Backpropagated expectation value without truncation: 0.8790435084647706
Backpropagated expectation value with truncation: 0.8759838342768448
- Observed Error for observable without truncation: 6.373e-03
- Observed Error for truncated observable: 9.432e-03
الخطوات التالية
- جرّب البرنامج التعليمي حول استخدام OBP لتحسين القيم المتوقعة.