انتقل إلى المحتوى الرئيسي
هذا المشروع في مرحلة تجريبية. وجدت خطأ؟ افتح مشكلة على GitHub. لديك سؤال أو فكرة؟ اسأل في Discussions — نحب سماع ملاحظاتك!
كيف جودة الترجمة في هذه الصفحة؟

اقتطاع حدود باولي أثناء الانتشار العكسي

يشرح هذا الدليل كيفية ضبط آلية اقتطاع حدود باولي التي توفرها الوحدة qiskit_addon_obp.utils.truncating.

يمكن استخدام الانتشار العكسي للمؤثرات (OBP) لتقليل عمق الدوائر الكمومية على حساب مرصود أكثر تعقيدًا. وللحصول على نتائج ذات معنى من OBP، يحتاج المرء عادةً إلى اقتطاع حدود من مرصوده لمنعه من النمو أكثر من اللازم. إحدى الطرق للسماح بانتشار عكسي أعمق في الدائرة مع منع المؤثر من النمو كثيرًا هي اقتطاع الحدود ذات المعاملات الصغيرة بدلًا من إضافتها إلى المؤثر. يمكن أن يؤدي اقتطاع الحدود إلى تقليل عدد الدوائر الكمومية المطلوب تنفيذها، لكنه يسبب بعض الخطأ في حساب القيمة المتوقعة النهائية، يتناسب مع حجم معاملات الحدود المقتطعة.

تأخذ الدالة backpropagate كائن TruncationErrorBudget اختياريًا، يضبط اقتطاع حدود باولي ذات الوزن المنخفض لكل مرصود بعد نجاح الانتشار العكسي لكل شريحة. يعتمد عدد الحدود المقتطعة على معاملات ضبط متنوعة يحددها المستخدم. حاليًا، تتوفر استراتيجية اقتطاع واحدة فقط - هي الدالة truncate_binary_search. بالنظر إلى مرصود و_ميزانية_ معينة، تجري هذه الدالة بحثًا ثنائيًا على حدود باولي ومعاملاتها داخل ذلك المرصود لإيجاد العتبة المثلى، بحيث يكون مجموع المعاملات المقتطعة أكبر ما يمكن لكن أقل من الميزانية.

ملاحظة: افتراضيًا، يُستخدم معيار L1 لتقييم خطأ الاقتطاع وتقييده؛ لكن الإعداد p_norm يتيح تحديد معيار Lp المراد استخدامه. لمزيد من المعلومات حول كيفية استخدام هذا الإعداد، راجع دليل استخدام معايير Lp مختلفة لاقتطاع حدود باولي. توضّح الأمثلة التالية طرقًا متنوعة لبناء TruncationErrorBudget باستخدام الدالة المرافقة setup_budget.

بناء دائرة مثال​

يستخدم هذا الدليل شرائح الدائرة التالية:

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types

# Generate a linear chain of 10 qubits
linear_chain = rustworkx.generators.path_graph(10)

# Use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# Evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)

# for visualization purposes, recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)

Output of the previous code cell

سننظر في مرصود واحد بسيط:

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp("IIIIIZIIII")

أبسط حالة: ميزانية اقتطاع ثابتة لكل شريحة​

يمكن أن تختلف الميزانية المتاحة لاقتطاع حدود باولي في كل خطوة من خطوات الانتشار العكسي. ولفهم كيف يعمل ذلك، ننظر أولًا إلى أبسط حالة، وهي ميزانية اقتطاع ثابتة يحددها المستخدم.

أبسط طريقة لتحديد ميزانية الاقتطاع هي استخدام الوسيط max_error_per_slice. في الواقع، هذا ما يجري في درس تقليل عمق الدوائر باستخدام الانتشار العكسي للمؤثرات. يؤدي ضبط max_error_per_slice على قيمة float إلى منح كل شريحة ميزانية تساوي تلك القيمة. في المثال أدناه، نضبط هذه القيمة على 0.001 مما يضمن خطأ اقتطاع ضمنيًا لا يزيد عن 0.018، لو جرى الانتشار العكسي لجميع الشرائح الـ 18.

لاحظ أن أي ميزانية خطأ متبقية بعد الانتشار العكسي لشريحة واقتطاع الحدود ذات المعاملات الصغيرة ستُضاف دائمًا إلى ميزانية خطأ الشريحة التالية.

from qiskit_addon_obp.utils.truncating import setup_budget

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 29 terms and 10 commuting groups.

نستخدم بعد ذلك كائن OBPMetadata والأدوات التي توفرها وحدة visualization لتصوير عملية الانتشار العكسي.

  • الرسم العلوي الأيسر يُظهر أن لدينا ميزانية كافية لبدء اقتطاع حدود المرصود بعد الانتشار العكسي للشريحة الثالثة. ابتداءً من الشريحة الثالثة، نعرف أننا نقتطع حدًا واحدًا على الأقل من كل شريحة ننشرها عكسيًا لأننا نتكبد بعض خطأ الاقتطاع بعد كل شريحة.

  • الرسم العلوي الأيمن يُظهر أن ميزانية الخطأ تنمو إلى .003 للشريحة الثالثة. نرى انخفاضًا حادًا في الميزانية المتبقية، مما يعني أن حدودًا اقتُطعت من المرصود. وهذا يتسق مع ما استنتجناه من الرسم العلوي الأيسر.

  • الرسم السفلي الأيسر يُظهر أنه مع اقتطاع حدود من مرصودنا، ينمو خطؤنا المتراكم الكلي بشكل رتيب. يؤكد هذا الرسم أيضًا أنه لم تُقتطع أي حدود إلا بعد الانتشار العكسي للشريحة الثالثة.

  • الرسم السفلي الأيمن يُظهر أن عدد مجموعات باولي المتبادلة التبديل في مرصودنا قد نما إلى الحد المحدد وهو 10. يُظهر هذا الرسم أيضًا كيف أن الانتشار العكسي لطبقة إضافية واحدة سيجعل مرصودنا يتجاوز الحد المحدد، كما يتضح من تقاطع الخطين الأسود والأحمر.

لاحظ أنه في جميع هذه الرسوم، يعدّد المحور السيني الشرائح المنتشرة عكسيًا، لكن بما أن OBP يعمل على نهاية الدائرة، فإن slice 1 هي الشريحة الأخيرة تمامًا، وslice 2 هي التي قبلها، وهكذا.

from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_left_over_error_budget,
plot_num_qwc_groups,
plot_slice_errors,
)

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

تحديد ميزانية كل شريحة صراحةً​

إذا كنت تعرف كيف تعيّن ميزانية لكل شريحة بحيث يكون أداء الانتشار العكسي أمثل، فقد ترغب في تعيين ميزانية صريحة لكل شريحة. لأغراض العرض، سنعيّن للشرائح الثلاث الأولى ميزانية صفرية ونستخدم ميزانية قدرها .001 لكل شريحة من الشرائح المتبقية.

# Zero out the first 3 slices' budgets
max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
New observable contains 32 terms and 10 commuting groups.

أدى إزالة الميزانية من الطبقات الثلاث الأولى إلى عدم اقتطاع أي حدود إلا بعد الطبقة الرابعة، كما يمكن التأكد منه في ثلاثة من هذه الرسوم. ما قد يكون مفاجئًا إلى حد ما هو أنه رغم أن الشريحة الرابعة لم تتلقَّ أي ميزانية متبقية، فقد اقتُطع حد باستخدام الميزانية المخصصة .001. هذا واضح من الرسم العلوي الأيسر، ويمكن ملاحظته أيضًا في الرسم العلوي الأيمن، إذ يتسطح منحنى الميزانية المتبقية بين الشريحتين الثالثة والرابعة. من التفاصيل اللافتة أيضًا أن حدًا واحدًا على الأقل اقتُطع من هذه النقطة فصاعدًا، كما في المثال السابق.

الخلاصة الأساسية هي أنه رغم أننا تكبدنا خطأ اقتطاع أقل في المثال الثاني بسبب تصفير ميزانيات بعض الشرائح، فقد تمكنا من الانتشار العكسي لنفس عدد الشرائح، ويحتوي مرصودنا على نفس عدد مجموعات باولي المتبادلة التبديل. يمكننا التأكد من أن حد الخطأ لدينا أصغر في المثال الثاني بفحص الرسم السفلي الأيسر.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

تحديد الميزانية بشكل دوري​

إذا كانت لديك دائرة ذات نمط متكرر، مثل دائرة تروتر، فقد ترغب في تحديد ميزانية لهذه المجموعة المتكررة من الشرائح واستخدام تلك الميزانية لجميع التكرارات اللاحقة لتلك الشرائح.

بشكل أكثر تحديدًا، تحتوي الدائرة المثال التي نستخدمها على ست شرائح تتكرر ثلاث مرات، بإجمالي 18 شريحة. سنعيّن بشكل اعتباطي ميزانية صفرية للطبقات أحادية الكيوبت وطبقات RYY، وميزانية .003 لكل طبقة من طبقات RXX. سنلاحظ كيف يؤدي تحديد الميزانية كتسلسل بطول 6 إلى تطبيقها على الشرائح الـ 18 بشكل دوري.

مرة أخرى، لاحظ أن الشرائح تُنشر عكسيًا بترتيب معاكس (أي بدءًا من النهاية). لذلك فإن أول إدخال في ميزانيتنا الدورية يُستخدم فعليًا للشريحة الأخيرة، والإدخال الثاني للشريحة التي قبلها، وهكذا.

# Specify a length-6 per-slice budget.
# This will be cycled over three times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)
op_budget = OperatorBudget(max_qwc_groups=20)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 13 circuit slices.
New observable contains 49 terms and 14 commuting groups.

كما يظهر في الصورتين العلوية اليسرى والسفلية اليسرى، لم يُجرَ أي اقتطاع على الشرائح الأربع الأولى لأنه لم تُخصَّص لها ميزانية خطأ. أما الشريحتان الخامسة والسادسة فقد اقتُطعت بعض حدودهما عندما أصبحت الميزانية متاحة.

كما يظهر في الصورة العلوية اليسرى، حدث قدر كبير نسبيًا من الخطأ بعد الانتشار العكسي للشريحة السابعة، رغم أنه لم تُخصَّص لها ميزانية. وذلك لأن نحو .002 فقط من إجمالي الميزانية .006 المخصصة للشريحتين الخامسة والسادسة استُخدم، فحُوِّل الباقي إلى الشريحة السابعة وأُنفق معظمه، كما يظهر في الصورة العلوية اليمنى.

تُظهر الصورتان العلوية اليسرى والعلوية اليمنى أن القدر الصغير من الميزانية المتبقية يُستنفد بين الشرائح 8-10، وتصبح ميزانية جديدة متاحة في الشريحة 11، كما هو متوقع. وتُظهر الصور العلوية اليسرى والعلوية اليمنى والسفلية اليسرى جميعها السلوك الدوري للوسيط max_error_per_slice عندما يكون طوله أقل من عدد الشرائح. كان هذا السلوك الدوري سيستمر عبر جميع الشرائح، لكن معيار التوقف max_qwc_groups تحقق بعد الانتشار العكسي لـ 13 شريحة، كما يظهر في الصورة السفلية اليمنى.

ومن اللافت أيضًا أن عدد مجموعات باولي ينخفض فعليًا بعد الجولة الثانية من توفر الميزانية عند الشريحة 11. وذلك لأنه كانت هناك مجموعات بمعاملات صغيرة لم يكن بالإمكان اقتطاعها حتى توفرت ميزانية أكبر بعد الانتشار العكسي للشريحة 11، فتراكمت في المرصود لعدة تكرارات. تُبرز هذه الحالة أيضًا أنه يجب تجاوز max_qwc_groups حتى تنتهي الخوارزمية.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

تحديد سقف للخطأ الكلي​

بالإضافة إلى تحديد ميزانية الخطأ لكل شريحة، يمكن تحديد الحد الأقصى للخطأ الناتج عن الاقتطاع. وبمجرد بلوغ هذا الحد، لن يُجرى أي اقتطاع آخر؛ لكن الانتشار العكسي سيستمر حتى يصبح المرصود كبيرًا جدًا ويتحقق أحد معايير التوقف.

نعيد الآن تشغيل التجربة أعلاه، مع تحديد سقف لـ max_error_total بحيث تُستنفد ميزانية الخطأ بعد الانتشار العكسي للشريحة السابعة.

# Specify a length-6 per-slice budget.
# This will be cycled over 3 times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2

truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice, max_error_total=0.006
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 10 circuit slices.
New observable contains 67 terms and 20 commuting groups.

كما هو متوقع، يبلغ خطأ الاقتطاع سقفه عند .006 (المخطط السفلي الأيسر). ومن اللافت أننا في هذا التشغيل لم نتمكن من الانتشار العكسي للشريحة 11. وذلك لأنه لم تكن لدينا ميزانية كافية لاقتطاع الحدود وتجاوز عدد مجموعات باولي المتبادلة التبديل الحد 20، كما يظهر في الصورة السفلية اليمنى.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

قد يكون من المرغوب ببساطة تحديد سقف للميزانية الإجمالية باستخدام max_error_total دون تحديد max_error_per_slice.

truncation_error_budget = setup_budget(max_error_total=0.018)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)

قد يكون ناتج الخلية أعلاه مفاجئًا بعض الشيء لأن per_slice_budget مضبوطة على max_error_total. ويدل هذا على أن الميزانية المتاحة بأكملها ستُستهلك بشراهة. يمكنك التفكير في الأمر هكذا: الميزانية بأكملها متاحة لكل شريحة (لأننا نكرر على per_slice_budget). لكن أي ميزانية استُهلكت بالفعل تُخصم من الميزانية المتاحة عند تلك النقطة من الخوارزمية.

op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 9 circuit slices.
New observable contains 25 terms and 9 commuting groups.

تُظهر الصورة العلوية اليمنى كيف تُتاح ميزانية الخطأ .018 بأكملها للشريحة الأولى. لا يُجرى أي اقتطاع حتى الشريحة الثالثة، لذا تبقى الميزانية المتبقية ثابتة. تتناقص الميزانية بشكل رتيب، إذ تُتاح الميزانية الكاملة لكل شريحة منتشرة عكسيًا حتى تُستنفد.

من اللافت أن هذه التجربة أنتجت شريحتين أقل منتشرتين عكسيًا مقارنةً بالتجربة الأولى في هذا الدفتر، التي تكاد تكون مطابقة لها. يُظهر هذا أنه في بعض المسائل قد يكون توزيع الميزانية بالتساوي هو الأمثل. وفي مسائل أخرى، قد يؤدي السماح للشرائح بإنفاق الميزانية الكاملة بشراهة إلى أداء أفضل.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

تحديد سقف لعدد الشرائح المنتشرة عكسيًا والخطأ الكلي معًا​

إذا كنت لا تريد توزيع ميزانية الخطأ على امتداد الدائرة كلها لكنك لا تريد إنفاقها بشراهة أيضًا، فيمكنك تحديد عدد الشرائح التي تنوي نشرها عكسيًا (num_slices)، مع ميزانية خطأ كلية (max_error_total). سيوزع هذا ميزانية الخطأ بشكل منتظم (وفقًا لـ p_norm) على الشرائح المدخلة.

هنا، سنحدد سقفًا لعدد الشرائح التي قد ننشرها عكسيًا عند 12، وسنُبقي على نفس ميزانية الخطأ الكلية.

num_slices = 12

truncation_error_budget = setup_budget(
max_error_total=0.018, num_slices=num_slices, p_norm=1
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)

الآن سنحاول الانتشار العكسي للشرائح الـ 12 التي خصصنا لها بعض الميزانية في الخطوة السابقة. نفعل ذلك بتمرير آخر 12 شريحة في دائرتنا فقط. مع p_norm=1، تكون الميزانية المتاحة لكل شريحة من الشرائح الـ 12 هي 0.018 / num_slices = 0.0015.

bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices[-num_slices:],
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)

بما أننا مررنا مجموعة جزئية من شرائحنا (في الكود slices[-num_slices:]) إلى backpropagate، يجب علينا دمج الشرائح المتبقية بعد الانتشار العكسي مع الشرائح التي لم تُرسل أبدًا للانتشار العكسي (في الكود slices[:-num_slices]).

بعد دمج جميع الشرائح المتبقية، يمكننا استخدام combine_slices لتوليد QuantumCircuit ذات عمق مخفَّض. بعد ذلك، نفحص عدد الشرائح التي نُشرت عكسيًا مقابل مدى كبر مرصودنا.

# Recombine the slices remaining after backprop with the rest of the original circuit
reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)

print(f"Backpropagated {num_slices - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 12 circuit slices.
New observable contains 29 terms and 9 commuting groups.

توضح الرسوم البيانية أننا نجحنا في الانتشار العكسي لجميع الشرائح الـ 12 مع إبقاء المراقَب (observable) ضمن أقل من 10 مجموعات باولي (Pauli) متبادلة الإبدال. ويمكننا أيضًا أن نرى أن استخدام num_slices مع max_error_total ينتج توزيعًا للميزانيات عبر الشرائح، وأن الميزانية غير المستخدمة تُمرَّر مرة أخرى إلى الشريحة التالية. ويظهر ذلك بوضوح أكبر في الرسم العلوي الأيمن، حيث تُستهلك الميزانية وتُعوَّض طوال عملية الانتشار العكسي.

من الملفت أن طريقة توزيع الخطأ هذه أعطت أفضل النتائج (شرائح أكثر تم انتشارها عكسيًا) مقارنةً بالتجربة الأولى في هذا الدفتر والمثال السابق مباشرةً لهذا المثال. كل هذه الأمثلة خصصت ميزانية خطأ قدرها .018، لكن الانتشار العكسي تصرف بشكل مختلف حسب طريقة توزيع هذه الميزانية.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell

العمل مع عدة مراقَبات​

تسمح الدالة qiskit_addon_obp.backpropagate بتمرير تسلسل من المراقَبات. وهذا يبسّط سير العمل عند التعامل مع عدة مراقَبات مستهدفة.

نذكر هذا هنا صراحةً لنوضح لك كيف تتعامل استراتيجية الاقتطاع مع هذه الحالة. ولغرض هذا المثال، نضيف مراقَبًا إضافيًا إلى المراقَب الذي كنا نستخدمه حتى الآن:

obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]

أخيرًا، نكرر التجربة الأولى من هذا الدرس، لكن مع مراقَبين لنشر الدائرة عكسيًا إليهما.

في هذا المثال، لا يؤثر ذلك على عدد الشرائح التي أمكن نشرها عكسيًا. لكن يمكننا أن نرى أن المراقَبين أنتجا أعدادًا مختلفة من حدود باولي والمجموعات المتبادلة الإبدال.

truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups."
)
print(
f"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups."
)
Backpropagated 11 circuit slices.
The new first observable contains 29 terms and 10 commuting groups.
The new second observable contains 23 terms and 8 commuting groups.

تساعد الرسوم البيانية أدناه على توضيح كيفية تعامل خوارزمية الانتشار العكسي مع عدة مراقَبات.

أولًا، توضح الرسوم في أعلى اليسار وأعلى اليمين وأسفل اليسار أن ميزانية اقتطاع الحدود تُحدَّد بشكل منفصل لكل مراقَب. بعبارة أخرى، يستطيع كلا المراقَبين اقتطاع الحدود بافتراض خطأ قدره 0.001 لكل شريحة منتشرة عكسيًا. ونظرًا لاختلاف طبيعة المراقَبين، ينتج عن ذلك استهلاك مختلف للميزانية. في هذا المثال، يظهر تداخل كبير بينهما، وهذا لا يحدث دائمًا.

يوضح الرسم السفلي الأيمن أن max_qwc_groups تأخذ في الحسبان جميع المراقَبات. أي أن حدود كل المراقَبات تُجمَّع معًا للوصول إلى عدد نهائي واحد من المجموعات المتبادلة الإبدال على مستوى الكيوبتات، ثم تتم مقارنته بـ max_qwc_paulis. ويحدث الشيء نفسه مع حد max_paulis (غير مشروح في هذا الدفتر)، الذي يتيح لك تحديد حد أقصى لعدد حدود باولي عبر جميع المراقَبات.

fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])

Output of the previous code cell