الانتشار الخلفي للمؤثرات (OBP) لتقدير قيم التوقع
تقدير الاستخدام: 4 دقائق على معالج Heron r3 (ملاحظة: هذا تقدير فحسب. قد يختلف وقت التشغيل الفعلي لديك.)
نتائج التعلم
بعد إتمام هذا الدليل التعليمي، يجب أن يفهم المستخدمون:
- كيفية استخدام
qiskit-addon-obpلتقليل عمق الدائرة الكمية على حساب زيادة عدد تنفيذات الدائرة - كيفية استخدام
qiskit-addon-utilsلبناء هاملتونيات XYZ ودوائر تطورها الزمني
المتطلبات المسبقة
نقترح أن يكون المستخدمون على دراية بالمواضيع التالية قبل الشروع في هذا الدليل:
- استخدام الأداة الأولية Estimator لحساب قيم التوقع للمؤثرات
الخلفية النظرية
الانتشار الخلفي للمؤثرات هو أسلوب يقوم على استيعاب العمليات من نهاية الدائرة الكمية في المؤثر المقاس، مما يُقلّص عمق الدائرة بشكل عام على حساب زيادة عدد الحدود في المؤثر. والهدف هو إعادة الانتشار الخلفي لأكبر قدر ممكن من الدائرة دون السماح للمؤثر بالنمو بشكل مفرط. يتوفر تطبيق قائم على Qiskit في إضافة OBP الخاصة بـ Qiskit. اقرأ التوثيق المقابل للمزيد من المعلومات.
لنأخذ مثالاً لدائرة يُراد فيها قياس المؤثر ، حيث هي عوامل Pauli و هي المعاملات. لنُرمز للدائرة بالوحدوية الأحادية التي يمكن تقسيمها منطقياً إلى كما هو موضح في الشكل أدناه.

يستوعب الانتشار الخلفي للمؤثرات الوحدوية في المؤثر عن طريق تطويرها على النحو . بمعنى آخر، يُنفَّذ جزء من الحساب كلاسيكياً عبر تطور المؤثر من إلى . يمكن الآن إعادة صياغة المسألة الأصلية على أنها قياس المؤثر للدائرة ذات العمق الأصغر التي وحدويتها .
تُمثَّل الوحدوية كعدد من الشرائح . وتوجد طرق متعددة لتعريف الشريحة؛ فعلى سبيل المثال في الدائرة أعلاه، يمكن اعتبار كل طبقة من بوابات وكل طبقة من بوابات شريحةً مستقلة. يشمل الانتشار الخلفي حساب كلاسيكياً. يمكن تمثيل كل شريحة على النحو ، حيث هو عامل Pauli على كيوبت و هو قيمة عددية. ومن السهل التحقق من أن:
في المثال أعلاه، إذا كان ، فإننا نحتاج إلى تنفيذ دائرتين كميتين بدلاً من واحدة لحساب قيمة التوقع. لذا، قد يزيد الانتشار الخلفي عدد الحدود في المؤثر، مما يؤدي إلى عدد أكبر من عمليات تنفيذ الدوائر. إحدى الطرق للسماح بانتشار خلفي أعمق في الدائرة مع منع نمو المؤثر بشكل مفرط هي اقتطاع الحدود ذات المعاملات الصغيرة بدلاً من إضافتها إلى المؤثر. فعلى سبيل المثال في المثال أعلاه، قد يُختار اقتطاع الحد المتضمن شريطة أن يكون صغيراً بما يكفي. يؤدي اقتطاع الحدود إلى تقليل عدد الدوائر الكمية الواجب تنفيذها، غير أن ذلك يُحدث خطأً في حساب قيمة التوقع النهائية يتناسب مع حجم معاملات الحدود المقتطعة.
المتطلبات
قبل البدء في هذا الدليل التعليمي، تأكد من تثبيت ما يلي:
- Qiskit SDK الإصدار 2.0 أو أحدث، مع دعم التصور
- Qiskit Runtime الإصدار 0.22 أو أحدث (
pip install qiskit-ibm-runtime) - إضافة OBP الخاصة بـ Qiskit الإصدار 0.3 أو أحدث (
pip install qiskit-addon-obp) - أدوات Qiskit الإضافية الإصدار 0.3 أو أحدث (
pip install qiskit-addon-utils)
الإعداد
# 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
مثال المحاكي الصغير الحجم
ينفّذ هذا الدليل التعليمي نمط Qiskit لمحاكاة الديناميكا الكمية لسلسلة دوران Heisenberg باستخدام إضافة OBP الخاصة بـ Qiskit. لاحظ أنه في محاكٍ خالٍ من الضوضاء، تكون قيمة التوقع المحصّلة مع الانتشار الخلفي ودونه متطابقة.
الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمية
تحويل التطور الزمني لنموذج Heisenberg الكمي إلى تجربة كمية
أولاً، سنستخدم دالة generate_xyz_hamiltonian من qiskit-addon-utils لتوليد هاملتوني شبيه بـ Heisenberg على رسم بياني لشبكة اتصال محددة. يمكن أن يكون هذا الرسم البياني إما rustworkx.PyGraph أو CouplingMap. فيما يلي، سنستخدم خريطة اقتران CouplingMap خطية مكوّنة من 10 كيوبتات.
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")
بعد ذلك، نُولّد مؤثر Pauli يُمثّل هاملتونيان Heisenberg XYZ:
حيث هو الرسم البياني لخريطة الاقتران. في هذا الدليل، استخدمنا بقيم على التوالي، و بقيم على التوالي.
# 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])
انطلاقاً من مؤثر الكيوبت، يمكننا توليد دائرة كمية تُنمذج تطوره الزمني. استخدمنا generate_time_evolution_circuit مع تحليل Lie Trotter لبناء دائرة التطور الزمني.
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=2),
)
circuit.draw("mpl", style="iqp", fold=-1)

الخطوة 2: تحسين المسألة لتنفيذها على العتاد الكمي
إنشاء شرائح الدائرة لإجراء الانتشار الخلفي
تُعيد دالة backpropagate الانتشار الخلفي لشرائح كاملة في كل مرة، لذا فإن طريقة التقسيم قد تؤثر في كفاءة الانتشار الخلفي لمسألة بعينها. هنا سنُجمّع البوابات من النوع نفسه في شرائح باستخدام دالة slice_by_depth.
للاطلاع على نقاش أكثر تفصيلاً حول تقسيم الدوائر إلى شرائح، راجع دليل كيفية الخاص بحزمة 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.
تقييد الحد الأقصى لنمو المؤثر خلال الانتشار الخلفي
خلال الانتشار الخلفي، سيتجه عدد الحدود في المؤثر عموماً نحو بسرعة، حيث هو عدد الشرائح. عندما لا يتبادل حدّان في المؤثر التبديل على مستوى الكيوبتات، نحتاج إلى دوائر منفصلة للحصول على قيم التوقع المقابلة لكل منهما. فعلى سبيل المثال، إذا كان لدينا مؤثر يعمل على كيوبتين ، فبما أن ، يكفي قياس واحد في أساس واحد لحساب قيم التوقع لهذين الحدّين. غير أن يُضاد التبادل مع الحدّين الآخرين، لذا نحتاج إلى قياس في أساس منفصل لحساب قيمة التوقع لـ . بمعنى آخر، نحتاج إلى دائرتين بدلاً من واحدة لحساب . مع تزايد عدد الحدود في المؤثر، يرتفع احتمال زيادة العدد المطلوب من تنفيذات الدائرة أيضاً.
يمكن تحديد حد أقصى لحجم المؤثر عبر تمرير الوسيط operator_budget إلى دالة backpropagate، الذي يقبل نسخة من OperatorBudget.
للتحكم في الموارد الإضافية (عدد تنفيذات الدائرة، وبالتالي وقت QPU المطلوب) المُخصَّصة، نحدّد الحد الأقصى لعدد مجموعات Pauli المتبادلة كيوبتياً التي يُسمح للمؤثر المُعاد انتشاره الخلفي بامتلاكها. هنا نُحدّد أن الانتشار الخلفي يجب أن يتوقف حين يتجاوز عدد مجموعات Pauli المتبادلة كيوبتياً في المؤثر الحد ثمانية.
op_budget = OperatorBudget(max_qwc_groups=8)
إعادة الانتشار الخلفي للشرائح من الدائرة
أولاً نُحدّد المؤثر المقاس على أنه ، حيث هو عدد الكيوبتات. سنُعيد الانتشار الخلفي للشرائح من دائرة التطور الزمني حتى لا تتمكن حدود المؤثر من الاندماج ضمن ثماني مجموعات أو أقل من مجموعات Pauli المتبادلة كيوبتياً.
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])
ستلاحظ فيما يلي أننا أعدنا الانتشار الخلفي لست شرائح، وأن الحدود اندمجت في ست مجموعات لا ثماني. وهذا يعني أن إعادة الانتشار الخلفي لشريحة واحدة إضافية ستتجاوز حد الثماني مجموعات من Pauli. يمكننا التحقق من ذلك بفحص البيانات الوصفية المُعادة. لاحظ أيضاً أن تحويل الدائرة في هذا الجزء دقيق تماماً، أي أنه لم يُقتطع أي حد من المؤثر الجديد . الدائرة والمؤثر اللذان خضعا للانتشار الخلفي يُعطيان النتيجة ذاتها الناتجة عن الدائرة والمؤثر الأصليَّين.
# 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:
في مثال المحاكي الصغير الحجم، لن نستخدم الاقتطاع. ذلك لأنه في غياب الضوضاء، تُعطي الدائرة مع الانتشار الخلفي ودونه النتيجة نفسها، والاقتطاع يُفاقم النتيجة بسبب التقريب الإضافي الذي يُدخله.
ضبط الدوائر للتوافق مع مجموعة البوابات الأساسية
الآن نضبط كلاً من الدائرة الأصلية والدائرة التي خضعت للانتشار الخلفي لتتوافق مع بوابات الـ backend. لا نحتاج إلى الضبط على الـ backend الفعلي لأننا سنُشغّل المحاكي للمثال الصغير.
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)
الخطوة 3: التنفيذ باستخدام Qiskit primitives
أولاً، نُنشئ اثنين من Primitive Unified Blocs (PUBs) يُقابلان الدائرة الأصلية والدائرة التي خضعت للانتشار الخلفي. ثم نُنفّذ هذه الـ PUBs على Estimator مثالي للحصول على قيم التوقع.
pubs = [(isa_circuit, observable), (isa_bp_circuit, bp_obs)]
rng = np.random.default_rng()
estimator = StatevectorEstimator(seed=rng)
job = estimator.run(pubs)
الخطوة 4: المعالجة اللاحقة وإعادة النتيجة إلى الصيغة الكلاسيكية المطلوبة
الآن نحصل على قيم التوقع للدائرة الأصلية والدائرة التي خضعت للانتشار الخلفي.
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$')
كما هو متوقع، تتفق قيمتا التوقع. لأننا نشغّل على محاكٍ ذي متجه حالة خالٍ من الضوضاء، يُعدّ الانتشار الخلفي تحويلاً دقيقاً لزوج الدائرة-المؤثر، لذا يجب أن تُنتج سيرتا العمل الأصلية والتي خضعت للانتشار الخلفي القيمة ذاتها لـ . لا تتجلى فائدة الانتشار الخلفي إلا على العتاد الفعلي ذي الضوضاء، حيث تتراكم أخطاء أقل على الدائرة ذات العمق الأصغر الناتجة عن الانتشار الخلفي، كما يُبيّن مثال العتاد الكبير الحجم أدناه.
مثال العتاد الكبير الحجم
عند تطوير تجربة، من المفيد البدء بدائرة صغيرة لتسهيل التصور والمحاكاة. الآن ننتقل إلى الانتشار الخلفي للمؤثر على هاملتوني Heisenberg مكوّن من 50 كيوبت بالقيم ذاتها لمعاملات و والمؤثر ، لكن مع أربع خطوات Trotter. لا يمكن حساب قيمة التوقع المثالية بهذا الحجم بطريقة القوة الغاشمة، لذا نستخدم شبكة موترات ونحصل على قيمة التوقع المثالية .
إلى جانب الانتشار الخلفي، نُدخل في هذا المثال الكبير الانتشارَ الخلفي مع الاقتطاع. نريد مثالياً إعادة الانتشار الخلفي بأكبر قدر ممكن لتقليل عمق الدائرة الفعّالة. غير أن ذلك كثيراً ما يُفضي إلى عدد كبير من الحدود غير المتبادلة في المؤثر المُحدَّث، مما يُضاعف الحمل الكمي. لذا يمكننا حذف حدود المؤثر ذات المعاملات الصغيرة باستخدام ما يُعرف بالاقتطاع. وفي حين يُتيح الاقتطاع انتشاراً أعمق بتقليل عدد الحدود في المؤثر المُحدَّث، فإنه يُدخل بعض التقريب أيضاً. لذا من الضروري تقييد الاقتطاع ضمن حدود معينة كي لا يطغى خطأ التقريب على تقليل الضوضاء الناتج عن الانتشار الخلفي الأعمق.
للتحكم في مقدار الاقتطاع، نُخصّص ميزانية خطأ لكل شريحة وكذلك ميزانية خطأ إجمالية للدائرة بأكملها باستخدام دالة setup_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$')
الخطوات التالية
إن وجدت هذا العمل مثيراً للاهتمام، فقد تستفيد من المواد التالية:
- التجميع الكمي التقريبي لدوائر التطور الزمني
- صيغ متعددة الحدود لتقليل خطأ Trotter
pauli-prop، حزمة مُسرَّعة بـ Rust لانتشار Pauli، مع دروس تعليمية تُغطي OBP وتقدير قيمة التوقع الكلاسيكية والمحاكاة الضوضائية