قطع الأسلاك بصياغة تعليمة `Move` ثنائية Qubit
في هذا البرنامج التعليمي، سنعيد بناء قيم التوقع لدائرة Circuit مؤلفة من سبعة qubit، وذلك بتقسيمها إلى دائرتين Circuit كل منهما أربعة qubit باستخدام قطع الأسلاك.
فيما يلي الخطوات التي سنتبعها في هذا نمط Qiskit:
- الخطوة 1: تعيين المسألة إلى دوائر Circuit ومؤثرات كمومية:
- تعيين هاميلتونيان على دائرة Circuit كمومية.
- الخطوة 2: التحسين للعتاد المستهدف [تستخدم الإضافة الخاصة بالقطع]:
- قطع الدائرة Circuit والعنصر القابل للرصد.
- نقل التجارب الفرعية عبر Transpiler للعتاد.
- الخطوة 3: التنفيذ على العتاد المستهدف:
- تشغيل التجارب الفرعية الناتجة عن الخطوة 2 باستخدام البدائي
Sampler.
- تشغيل التجارب الفرعية الناتجة عن الخطوة 2 باستخدام البدائي
- الخطوة 4: المعالجة اللاحقة للنتائج [تستخدم الإضافة الخاصة بالقطع]:
- دمج نتائج الخطوة 3 لإعادة بناء قيمة التوقع للعنصر القابل للرصد المعني.
الخطوة 1: التعيين
إنشاء دائرة Circuit للقطع
أولاً، نبدأ بدائرة Circuit مستوحاة من الشكل 1(a) في arXiv:2302.03366v1.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
from qiskit import QuantumCircuit
qc_0 = QuantumCircuit(7)
for i in range(7):
qc_0.rx(np.pi / 4, i)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
qc_0.cx(3, 4)
qc_0.cx(3, 5)
qc_0.cx(3, 6)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
<qiskit.circuit.instructionset.InstructionSet at 0x7f16ab191a80>
qc_0.draw("mpl")

تحديد عنصر قابل للرصد
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp(["ZIIIIII", "IIIZIII", "IIIIIIZ"])
الخطوة 2: التحسين
إنشاء دائرة Circuit جديدة مع وضع تعليمات Move في مواضع القطع المطلوبة
بالنظر إلى الدائرة Circuit أعلاه، نريد وضع قطعَين للأسلاك على خط qubit الأوسط، بحيث يمكن فصل الدائرة Circuit إلى دائرتين Circuit كل منهما أربعة qubit. إحدى طرق تحقيق ذلك هي وضع تعليمات Move ثنائية qubit يدوياً تنقل الحالة من سلك qubit إلى آخر. وتعليمة Move مكافئة مفاهيمياً لعملية إعادة ضبط على qubit الثاني، تليها بوابة Gate من نوع SWAP. ويتمثل أثر هذه التعليمة في نقل حالة qubit الأول (المصدر) إلى qubit الثاني (الوجهة)، مع استبعاد الحالة الواردة لـ qubit الثاني. ولكي يعمل هذا على النحو المقصود، من الضروري أن لا يشترك qubit الثاني (الوجهة) في أي تشابك مع بقية النظام؛ وإلا فإن عملية إعادة الضبط ستتسبب في انهيار جزئي لحالة بقية النظام.
هنا، نبني دائرة Circuit جديدة بإضافة qubit واحد إضافي مع وضع عمليات Move في مكانها. في هذا المثال، يمكننا إعادة استخدام qubit واحد: يصبح qubit المصدر لأول تعليمة Move هو qubit الوجهة لتعليمة Move الثانية.
ملاحظة: كبديل للتعامل المباشر مع تعليمات Move، يمكن اختيار تحديد قطع الأسلاك باستخدام تعليمة CutWire أحادية qubit. توجد دالة cut_wires لتحويل تعليمات CutWire إلى تعليمات Move على qubit مخصصة حديثاً. غير أنه على عكس الطريقة اليدوية، لا تتيح هذه الطريقة التلقائية إعادة استخدام أسلاك qubit. راجع دليل الإرشادات الخاص بـ CutWire للاطلاع على التفاصيل.
from qiskit_addon_cutting.instructions import Move
qc_1 = QuantumCircuit(8)
for i in [*range(4), *range(5, 8)]:
qc_1.rx(np.pi / 4, i)
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.append(Move(), [3, 4])
qc_1.cx(4, 5)
qc_1.cx(4, 6)
qc_1.cx(4, 7)
qc_1.append(Move(), [4, 3])
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.draw("mpl")

إنشاء عنصر قابل للرصد يتوافق مع الدائرة Circuit الجديدة
يتوافق هذا العنصر القابل للرصد مع observable، لكن يجب مراعاة سلك qubit الإضافي الذي أُضيف بشكل صحيح (أي إدراج "I" في الفهرس 4). لاحظ أنه في Qiskit، يقابل تمثيل السلسلة النصية لـ qubit-0 الحرفَ الأيمن في سلسلة باولي.
observable_expanded = SparsePauliOp(["ZIIIIIII", "IIIIZIII", "IIIIIIIZ"])
فصل الدائرة Circuit والعناصر القابلة للرصد
كما في البرامج التعليمية السابقة، سيُجمَّع qubit التي تحمل تسمية قسم مشتركة معاً، وستُقطع البوابات Gates غير المحلية الممتدة عبر أكثر من قسم.
from qiskit_addon_cutting import partition_problem
partitioned_problem = partition_problem(
circuit=qc_1, partition_labels="AAAABBBB", observables=observable_expanded.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases
تصور المسألة المحللة
subobservables
{'A': PauliList(['IIII', 'ZIII', 'IIIZ']),
'B': PauliList(['ZIII', 'IIII', 'IIII'])}
subcircuits["A"].draw("mpl")

subcircuits["B"].draw("mpl")

حساب عبء أخذ العينات للقطوع المختارة
نقطع هنا سلكَين، مما ينتج عنه عبء أخذ عينات يساوي .
لمزيد من المعلومات حول عبء أخذ العينات الناجم عن قطع الدائرة Circuit، راجع المواد التوضيحية.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 256.0
توليد التجارب الفرعية للتشغيل على Backend
تقبل دالة generate_cutting_experiments وسيطَي circuits/observables على شكل قواميس تعيّن تسميات أقسام qubit إلى subcircuit/subobservables المقابلة لها.
لمحاكاة قيمة التوقع للدائرة Circuit بحجمها الكامل، يُولَّد عدد كبير من التجارب الفرعية من التوزيع الاحتمالي شبه الكمومي المشترك للبوابات Gates المحللة، ثم تُنفَّذ على Backend واحد أو أكثر. يتحكم num_samples في عدد العينات المأخوذة من التوزيع، ويُعطى معامل مدمج واحد لكل عينة فريدة. لمزيد من المعلومات حول كيفية حساب المعاملات، راجع المواد التوضيحية.
from qiskit_addon_cutting import generate_cutting_experiments
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)
اختيار Backend
نستخدم هنا Backend وهمياً، مما سيؤدي إلى تشغيل Qiskit Runtime في الوضع المحلي (أي على محاكي محلي).
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
إعداد التجارب الفرعية للـ Backend
يجب نقل الدوائر Circuit عبر Transpiler مع تحديد Backend كهدف قبل إرسالها إلى Qiskit Runtime.
from qiskit.transpiler import generate_preset_pass_manager
# Transpile the subexperiments to ISA circuits
pass_manager = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_subexperiments = {
label: pass_manager.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
الخطوة 3: التنفيذ
تشغيل التجارب الفرعية باستخدام البدائي Sampler في Qiskit Runtime
from qiskit_ibm_runtime import SamplerV2, Batch
# Submit each partition's subexperiments to the Qiskit Runtime Sampler
# primitive, in a single batch so that the jobs will run back-to-back.
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
/home/garrison/Qiskit/qiskit-ibm-runtime/qiskit_ibm_runtime/session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
warnings.warn(
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
الخطوة 4: المعالجة اللاحقة
إعادة بناء قيمة التوقع
نعيد بناء قيم التوقع لكل حد من حدود العنصر القابل للرصد ثم ندمجها لإعادة بناء قيمة التوقع للعنصر القابل للرصد الأصلي.
from qiskit_addon_cutting import reconstruct_expectation_values
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)
مقارنة قيمة التوقع المعاد بناؤها بقيمة التوقع الدقيقة المستخرجة من الدائرة Circuit والعنصر القابل للرصد الأصليين
from qiskit_aer.primitives import EstimatorV2
estimator = EstimatorV2()
exact_expval = estimator.run([(qc_0, observable)]).result()[0].data.evs
print(f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}")
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}")
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 1.51319069
Exact expectation value: 1.59099026
Error in estimation: -0.07779957
Relative error in estimation: -0.04890009