تقليل عمق الدوائر باستخدام الانتشار العكسي للمؤثرات
الانتشار العكسي للمؤثرات هو أسلوب يتضمن استيعاب العمليات من نهاية دائرة Circuit كمية في مؤثر باولي، مما يُقلّل في الغالب من عمق الدائرة Circuit على حساب زيادة عدد الحدود في المؤثر. والهدف هو إعادة الانتشار العكسي بأكبر قدر ممكن من الدائرة Circuit دون السماح للمؤثر بالنمو المفرط.
إحدى طرق تعميق الانتشار العكسي في الدائرة Circuit مع منع المؤثر من الكبر المفرط، هي اقتطاع الحدود ذات المعاملات الصغيرة بدلاً من إضافتها إلى المؤثر. يمكن أن يؤدي اقتطاع الحدود إلى تقليل عدد الدوائر Circuits الكمية المطلوب تنفيذها، غير أن ذلك يُفضي إلى قدر من الخطأ في حساب قيمة التوقع النهائية يتناسب مع حجم معاملات الحدود المقتطعة. في هذا الدرس التطبيقي، سننفّذ نمط Qiskit لمحاكاة الديناميكيات الكمية لسلسلة دوران هايزنبرغ باستخدام الانتشار العكسي للمؤثرات:
- الخطوة 1: التعيين إلى المسألة الكمية
- تعيين هاميلتوني التطور الزمني على دائرة Circuit كمية
- الخطوة 2: تحسين المسألة
- تقطيع الدائرة Circuit
- إعادة انتشار الشرائح عكسياً من الدائرة Circuit إلى العنصر القابل للرصد من نوع باولي
- دمج الشرائح المتبقية في دائرة Circuit واحدة
- تحويل الدائرة Circuit عبر Transpiler للـ Backend
- الخطوة 3: تنفيذ التجارب
- حساب قيمة التوقع باستخدام الدائرة Circuit المختزلة والعنصر القابل للرصد الموسّع مع StatevectorEstimator تبسيطاً في هذا الدفتر
- الخطوة 4: إعادة بناء النتائج
- لا ينطبق.
ملاحظة: يصف Qiskit الطبقات بوصفها أقساماً بعمق 1 للدائرة Circuit عبر جميع Qubits. تستخدم هذه الحزمة مصطلح الشرائح للإشارة إلى طبقات ذات عمق اعتباطي. صُمّمت دالة qiskit_addon_obp.backpropagate للانتشار العكسي للشرائح الكاملة في المرة الواحدة، لذا يمكن أن يكون لاختيار طريقة تقطيع الدائرة Circuit تأثير كبير على أداء الانتشار العكسي لمسألة معينة. ستتعرف على الشرائح بمزيد من التفصيل أدناه.
الخطوة 1: التعيين إلى المسألة الكمية
تعيين التطور الزمني لنموذج هايزنبرغ الكمي إلى تجربة كمية
توفر حزمة qiskit_addon_utils وظائف قابلة لإعادة الاستخدام لأغراض متعددة.
توفر الوحدة qiskit_addon_utils.problem_generators دوالّ لتوليد هاميلتونيات شبيهة بهايزنبرغ على رسم بياني للتوصيل معين. يمكن أن يكون هذا الرسم البياني إما rustworkx.PyGraph أو CouplingMap مما يجعل استخدامه سهلاً في سير العمل المحورية حول Qiskit.
في ما يلي، نولّد أولاً CouplingMap بتوپولوجيا سداسية ثقيلة، ثم نستخرج منها سلسلة خطية من 10 Qubits. لاحظ أن مؤشرات reduced_coupling_map الجديدة تبدأ من الصفر.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-obp qiskit-addon-utils qiskit-ibm-runtime rustworkx
from qiskit.transpiler import CouplingMap
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])
from rustworkx.visualization import graphviz_draw
graphviz_draw(reduced_coupling_map.graph, method="circo")
بعد ذلك، نولّد مؤثر باولي يُنمذج هاميلتوني XYZ لهايزنبرغ.
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})$$ حيث $G(V,E)$ هو الرسم البياني لخريطة التوصيل المُعطاة. ```python import numpy as np from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian # 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), ) print(hamiltonian) ``` ```text 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]) ``` من مؤثر Qubit، يمكننا توليد دائرة Circuit كمية تُنمذج تطوره الزمني. مرة أخرى، تأتي الوحدة [qiskit_addon_utils.problem_generators](https://qiskit.github.io/qiskit-addon-utils/stubs/qiskit_addon_utils.problem_generators.html) بدالة ملائمة تؤدي هذا الغرض تحديداً: ```python from qiskit.synthesis import LieTrotter from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit circuit = generate_time_evolution_circuit( hamiltonian, time=0.2, synthesis=LieTrotter(reps=2), ) circuit.draw("mpl", style="iqp", scale=0.6) ```  ## الخطوة 2: تحسين المسألة \{#step-2-optimize-the-problem} ### إنشاء شرائح الدائرة Circuit للانتشار العكسي \{#create-circuit-slices-to-backpropagate} تذكّر أن دالة ``backpropagate`` ستُعيد الانتشار العكسي لشرائح الدائرة Circuit الكاملة في كل مرة، لذا يمكن أن يؤثر اختيار طريقة التقطيع على مدى جودة أداء الانتشار العكسي لمسألة معينة. هنا، سنجمع البوابات Gates من النوع نفسه في شرائح باستخدام دالة [slice_by_gate_types](https://qiskit.github.io/qiskit-addon-utils/stubs/qiskit_addon_utils.slicing.slice_by_gate_types.html). للاطلاع على نقاش أكثر تفصيلاً حول تقطيع الدوائر Circuits، راجع [دليل الإرشادات](https://qiskit.github.io/qiskit-addon-utils/how_tos/create_circuit_slices.html) الخاص بحزمة [qiskit-addon-utils](https://qiskit.github.io/qiskit-addon-utils/index.html). ```python from qiskit_addon_utils.slicing import slice_by_gate_types slices = slice_by_gate_types(circuit) print(f"Separated the circuit into {len(slices)} slices.") ``` ```text Separated the circuit into 18 slices. ``` ### تقييد حجم نمو المؤثر أثناء الانتشار العكسي \{#constrain-how-large-the-operator-may-grow-during-backpropagation} أثناء الانتشار العكسي، يميل عدد الحدود في المؤثر عموماً إلى الاقتراب بسرعة من $4^N$، حيث $N$ هو عدد Qubits. يمكن تحديد حجم المؤثر بتعيين الوسيط ``operator_budget`` في دالة ``backpropagate``، والذي يقبل نسخة من [OperatorBudget](https://qiskit.github.io/qiskit-addon-obp/stubs/qiskit_addon_obp.utils.simplify.OperatorBudget.html). هنا نحدد أن الانتشار العكسي يجب أن يتوقف عندما يتجاوز عدد مجموعات باولي المتبادلة بالنسبة إلى Qubit في المؤثر حد 8. ```python from qiskit_addon_obp.utils.simplify import OperatorBudget op_budget = OperatorBudget(max_qwc_groups=8) ``` ### إعادة انتشار الشرائح عكسياً من الدائرة Circuit \{#backpropagate-slices-from-the-circuit} أولاً، سنحدد العنصر القابل للرصد باولي-Z على Qubit رقم 0، ثم نُعيد انتشار الشرائح عكسياً من دائرة Circuit التطور الزمني حتى يتعذر دمج الحدود في العنصر القابل للرصد في 8 مجموعات متبادلة بالنسبة إلى Qubit أو أقل. ستلاحظ أدناه أننا أعدنا انتشار 7 شرائح عكسياً لكننا استخدمنا 6 فقط من مجموعات باولي الثماني المخصصة. يعني ذلك أن إعادة انتشار شريحة أخرى سيتجاوز الحد الأقصى البالغ 8 مجموعات. يمكننا التحقق من ذلك بفحص البيانات الوصفية المُرجعة. ```python from qiskit.quantum_info import SparsePauliOp from qiskit_addon_obp import backpropagate from qiskit_addon_utils.slicing import combine_slices # Specify a single-qubit observable observable = SparsePauliOp("IIIIIIIIIZ") # 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, include_barriers=True) print(f"Backpropagated {metadata.num_backpropagated_slices} slices.") print( f"New observable has {len(bp_obs.paulis)} terms, which can be combined into {len(bp_obs.group_commuting(qubit_wise=True))} groups." ) 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." ) print("The remaining circuit after backpropagation looks as follows:") bp_circuit.draw("mpl", scale=0.6) ``` ```text Backpropagated 7 slices. New observable has 18 terms, which can be combined into 8 groups. Note that backpropagating one more slice would result in 27 terms across 12 groups. The remaining circuit after backpropagation looks as follows: ```  بعد ذلك، سنحدد المسألة ذاتها مع القيود نفسها على حجم العنصر القابل للرصد الناتج. غير أننا هذه المرة سنخصص ميزانية خطأ لكل شريحة باستخدام دالة [setup_budget](https://qiskit.github.io/qiskit-addon-obp/stubs/qiskit_addon_obp.utils.truncating.setup_budget.html). ستُقتطع حدود باولي ذات المعاملات الصغيرة من كل شريحة حتى تمتلئ ميزانية الخطأ، ويُضاف الفائض إلى ميزانية الشريحة التالية. لتفعيل هذا الاقتطاع، يجب إعداد ميزانية الخطأ على النحو التالي: ```python from qiskit_addon_obp.utils.truncating import setup_budget truncation_error_budget = setup_budget(max_error_per_slice=0.005) ``` لاحظ أنه بتخصيص `5e-3` خطأ لكل شريحة للاقتطاع، أصبح بمقدورنا إزالة 3 شرائح إضافية من الدائرة Circuit، مع البقاء ضمن الميزانية الأصلية البالغة 8 مجموعات باولي متبادلة في العنصر القابل للرصد. بشكل افتراضي، يستخدم `backpropagate` معيار L1 للمعاملات المقتطعة لتحديد إجمالي الخطأ الناجم عن الاقتطاع. للاطلاع على خيارات أخرى، راجع [دليل الإرشادات حول تحديد p_norm](https://qiskit.github.io/qiskit-addon-obp/how_tos/bound_error_using_p_norm.html). في هذا المثال تحديداً، حيث أعدنا انتشار 10 شرائح عكسياً، يجب ألا يتجاوز إجمالي خطأ الاقتطاع ``(5e-3 error/slice) * (10 slices) = 5e-2``. لمزيد من التفاصيل حول توزيع ميزانية الخطأ عبر الشرائح، راجع [دليل الإرشادات هذا](https://qiskit.github.io/qiskit-addon-obp/how_tos/truncate_operator_terms.html). ```python # Run the same experiment but truncate observable terms with small coefficients bp_obs_trunc, remaining_slices_trunc, metadata = 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.num_backpropagated_slices} slices.") print( f"New observable has {len(bp_obs_trunc.paulis)} terms, which can be combined into {len(bp_obs_trunc.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." ) print("The remaining circuit after backpropagation looks as follows:") bp_circuit_trunc.draw("mpl", scale=0.6) ``` ```text 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. The remaining circuit after backpropagation looks as follows: ```  ### الآن بعد أن حصلنا على ansatz المختزل والعناصر القابلة للرصد الموسّعة، يمكننا تحويل تجاربنا عبر Transpiler إلى الـ Backend. \{#now-that-we-have-our-reduced-ansatze-and-expanded-observables-we-can-transpile-our-experiments-to-the-backend} سنستخدم هنا الـ [FakeMelbourneV2](https://quantum.cloud.ibm.com/docs/api/qiskit-ibm-runtime/fake-provider-fake-melbourne-v2) ذو الـ 14 Qubit من [qiskit-ibm-runtime](https://quantum.cloud.ibm.com/docs/api/qiskit-ibm-runtime) لتوضيح كيفية التحويل إلى Backend معالج كمي. ```python from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2 # Specify a backend and a pass manager for transpilation backend = FakeMelbourneV2() pm = generate_preset_pass_manager(backend=backend, optimization_level=1) # Transpile original experiment circuit_isa = pm.run(circuit) observable_isa = observable.apply_layout(circuit_isa.layout) # Transpile backpropagated experiment bp_circuit_isa = pm.run(bp_circuit) bp_obs_isa = bp_obs.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 = bp_obs_trunc.apply_layout(bp_circuit_trunc_isa.layout) ``` ## الخطوة 3: تنفيذ التجارب الكمية \{#step-3-execute-quantum-experiments} ### حساب قيمة التوقع \{#calculate-expectation-value} أخيراً، يمكننا تشغيل التجارب التي أُعيد فيها الانتشار العكسي ومقارنتها بالتجربة الكاملة باستخدام [StatevectorEstimator](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.primitives.StatevectorEstimator) الخالية من الضوضاء. يتضح أن قيمة التوقع التي أُعيد فيها الانتشار العكسي دون اقتطاع مكافئة للقيمة الدقيقة ضمن حدود الدقة العددية. تحتوي قيمة التوقع على المؤثر ذي الحدود المقتطعة على قدر من الخطأ بمقدار ``1e-4`` تقريباً، وهو ضمن الحد المسموح به. **ملاحظة:** نستخدم نظيراً ``Estimator`` مستنداً إلى متجه الحالة لتوضيح أثر الاقتطاع على الناتج. لتشغيل التجارب على الـ Backend الذي جرى التحويل إليه في الخطوة 2، يجب استيراد [EstimatorV2](https://quantum.cloud.ibm.com/docs/api/qiskit-ibm-runtime/estimator-v2) من ``qiskit-ibm-runtime`` وتمرير نسخة الـ Backend إلى المُنشئ. ```python from qiskit.primitives import StatevectorEstimator as Estimator estimator = Estimator() # Run the experiments using Estimator primitive result_exact = estimator.run([(circuit_isa, observable_isa)]).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: {result_bp}") print(f"Backpropagated expectation value with truncation: {result_bp_trunc}") print(f" - Expected Error for truncated observable: {metadata.accumulated_error(0):.3e}") print(f" - Observed Error for truncated observable: {abs(result_exact - result_bp_trunc):.3e}") ``` ```text Exact expectation value: 0.8854160687717507 Backpropagated expectation value: 0.8854160687717532 Backpropagated expectation value with truncation: 0.8850236647156059 - Expected Error for truncated observable: 4.933e-02 - Observed Error for truncated observable: 3.924e-04 ``` <TutorialFeedback />