انتقل إلى المحتوى الرئيسي

مقدمة إلى Qiskit

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

قبل البدء

اتبع تعليمات التثبيت والإعداد إن لم تكن قد فعلت ذلك بعد، بما في ذلك خطوات الإعداد لاستخدام منصة IBM Quantum™.

يُوصى باستخدام بيئة التطوير Jupyter للتفاعل مع الحواسيب الكمية. تأكد من تثبيت دعم التصور الإضافي الموصى به ('qiskit[visualization]'). ستحتاج أيضاً إلى حزمة matplotlib في الجزء الثاني من هذا المثال.

للتعرف على الحوسبة الكمية بشكل عام، قم بزيارة دورة أساسيات المعلومات الكمية في IBM Quantum Learning.

الاستيرادات

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime
# Import necessary modules for this notebook
import time
import qiskit

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector, plot_state_qsphere
from qiskit_aer import AerSimulator
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.visualization import plot_histogram
print(qiskit.__version__)
2.3.1

لتنفيذ الدوائر الكمية على الأجهزة الفعلية، تحتاج أولاً إلى إعداد حسابك. يمكنك القيام بذلك على النحو التالي:

  1. انتقل إلى منصة IBM Quantum® المُحدَّثة.
  2. انتقل إلى الزاوية اليمنى العلوية (كما هو موضح في الصورة أعلاه)، أنشئ رمز API الخاص بك وانسخه إلى موقع آمن.
  3. في الخلية التالية، استبدل deleteThisAndPasteYourAPIKeyHere بمفتاح API الخاص بك.
  4. انتقل إلى الزاوية اليسرى السفلى (كما هو موضح في الصورة أعلاه) وأنشئ نسختك. تأكد من اختيار الخطة المفتوحة.
  5. بعد إنشاء النسخة، انسخ رمز CRN المرتبط بها. قد تحتاج إلى التحديث لرؤية النسخة.
  6. في الخلية أدناه، استبدل deleteThisAndPasteYourCRNHere برمز CRN الخاص بك.

راجع هذا الدليل للمزيد من التفاصيل حول كيفية إعداد حساب IBM Cloud® الخاص بك.

⚠️ ملاحظة: تعامل مع مفتاح API الخاص بك كما تتعامل مع كلمة مرور آمنة. راجع دليل إعداد السحابة للحصول على مزيد من المعلومات حول استخدام مفتاح API في البيئات الآمنة وغير الموثوقة.

#your_api_key = "deleteThisAndPasteYourAPIKeyHere"
#your_crn = "deleteThisAndPasteYourCRNHere"

QiskitRuntimeService.save_account(
channel="ibm_quantum_platform",
token=your_api_key,
instance=your_crn,
overwrite=True
)

1. البوابات الكمية والدوائر الكمية

الدوائر الكمية هي نماذج للحوسبة الكمية حيث تكون العملية الحسابية سلسلة من البوابات الكمية. لنلقِ نظرة على بعض البوابات الكمية الشائعة.

بوابة X

تعادل بوابة X دورانًا حول محور X لكرة Bloch بمقدار π\pi راديان. تُحوّل 0|0\rangle إلى 1|1\rangle و1|1\rangle إلى 0|0\rangle. وهي المكافئ الكمي لبوابة NOT في الحواسيب الكلاسيكية وتُسمى أحياناً قلب البت.

X=(0110)X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix}

# Let's apply an X-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

بوابة H

تمثل بوابة هادامار دورانًا بمقدار π\pi حول المحور الواقع في منتصف المسافة بين محور XX ومحور ZZ. تُحوّل حالة الأساس 0|0\rangle إلى 0+12\frac{|0\rangle + |1\rangle}{\sqrt{2}}، مما يعني أن القياس سيعطي احتمالات متساوية بين 1 أو 0، مما يُنشئ "التراكب" للحالات. وتُكتب هذه الحالة أيضاً بالصيغة +|+\rangle.

H=12(1111)H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \\ \end{pmatrix}

# Let's apply an H-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

بوابة CX (بوابة CNOT)

تعمل بوابة NOT المتحكم بها (أو CNOT أو CX) على Qubitين. تُنفّذ عملية NOT (المكافئة لتطبيق بوابة X) على الـ Qubit الثاني فقط عندما يكون الـ Qubit الأول في الحالة 1|1\rangle، وإلا تتركه دون تغيير. ملاحظة: يرقّم Qiskit البتات في السلسلة من اليمين إلى اليسار.

CX=(1000010000010010)CX = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0\\ \end{pmatrix}

# Let's apply a CX-gate on |11>
qc = QuantumCircuit(2)
qc.x(0)
qc.x(1)
qc.cx(0,1)
qc.draw(output='mpl')

Quantum circuit diagram

sv=Statevector(qc)
plot_state_qsphere(sv)

Code output

إنشاء حالة Bell الأولى

ϕ+=12(00+11)|\phi^+ \rangle = \frac{1}{\sqrt 2}(|00 \rangle + |11 \rangle)

# Create a Bell state circuit

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0,1)

# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

# Plot the state using q-sphere visualization
sv = Statevector(qc)
plot_state_qsphere(sv)
# q-sphere is useful for visualizing states when Bloch sphere fails to

Code output

إنشاء حالة Bell الثانية

ϕ=12(0011)|\phi^- \rangle = \frac{1}{\sqrt 2}(|00 \rangle - |11 \rangle)

# Create a circuit with the second Bell state

qc = QuantumCircuit(2)
qc.x(0)
qc.h(0)
qc.cx(0,1)

qc.draw("mpl")

Quantum circuit diagram

التفسير هو:

H1=12(01)=H|1\rangle=\frac{1}{\sqrt{2} }(|0\rangle-|1\rangle) = |-\rangle
# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

إنشاء حالة GHZ ذات 3 Qubits

GHZ=12(000+111)|GHZ \rangle = \frac{1}{\sqrt 2}(|000 \rangle + |111 \rangle)

# Create a circuit with 3-qubit GHZ state

qc= QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)

qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

إنشاء حالة شعار Qiskit

Qiskit=12(0010+1101)|Qiskit \rangle = \frac{1}{\sqrt 2}(|0010 \rangle + |1101 \rangle)

Centered Image
# Create a circuit with the Qiskit logo state

qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.cx(0,3)
qc.x(1)

# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

2. إنشاء وتشغيل برنامج كمي بسيط

الخطوات الأربع لكتابة برنامج كمي باستخدام أنماط Qiskit هي:

  1. تعيين المسألة إلى تنسيق أصيل كمياً.

  2. تحسين الدوائر والمؤثرات.

  3. التنفيذ باستخدام دالة أولية كمية.

  4. تحليل النتائج.

2.1 تعيين المشكلة إلى تنسيق أصيل للحوسبة الكمومية

في البرنامج الكمومي، تُعدّ الدوائر الكمومية (Quantum Circuits) التنسيق الأصيل لتمثيل التعليمات الكمومية، فيما تمثّل المؤثرات (operators) المقادير الرصدية المراد قياسها. عند إنشاء Circuit، تُنشئ عادةً كائنًا جديدًا من نوع QuantumCircuit، ثم تُضيف إليه التعليمات بالتتابع.

تُنشئ خلية الشفرة التالية Circuit تُنتج حالة GHZ، وهي حالة تتشابك فيها ثلاثة Qubits تشابكًا كاملًا مع بعضها.

يستخدم Qiskit SDK ترقيم البت LSb 0، حيث تحمل الخانة nn القيمة 1n1 \ll n أو 2n2^n. لمزيد من التفاصيل، راجع موضوع ترتيب البتات في Qiskit SDK.

# Create a GHZ state circuit

qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

راجع QuantumCircuit في الوثائق للاطلاع على جميع العمليات المتاحة.

عند إنشاء الدوائر الكمومية، يجب مراعاة نوع البيانات المطلوب إرجاعها بعد التنفيذ. يوفر Qiskit طريقتين لإرجاع البيانات: يمكنك الحصول على توزيع احتمالي لمجموعة من Qubits تختارها للقياس، أو الحصول على قيمة التوقع لمقدار رصدي. جهّز حمل العمل لقياس Circuit بإحدى هاتين الطريقتين باستخدام Qiskit primitives (موضحة بالتفصيل في الخطوة 3).

يقيس هذا المثال قيم التوقع باستخدام الوحدة الفرعية qiskit.quantum_info، المحددة عبر المؤثرات (الكائنات الرياضية المستخدمة لتمثيل إجراء أو عملية تغير حالة كمومية). تُنشئ خلية الشفرة التالية ستة مؤثرات باولي ثلاثية Qubits: ZZZ وZZX وZII وXXI وZZI وIII.

# Set up six different observables.

observables_labels = ["ZZZ", "ZZX", "ZII", "XXI", "ZZI", "III"]

observables = [SparsePauliOp(label) for label in observables_labels]
print(observables)
[SparsePauliOp(['ZZZ'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZX'],
coeffs=[1.+0.j]), SparsePauliOp(['ZII'],
coeffs=[1.+0.j]), SparsePauliOp(['XXI'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZI'],
coeffs=[1.+0.j]), SparsePauliOp(['III'],
coeffs=[1.+0.j])]

هنا، المؤثر ZZI مثلًا هو اختصار للضرب التنسيقي ZZIZ\otimes Z\otimes I، أي قياس Z على Qubit 2 وZ على Qubit 1 معًا، واستخراج معلومات حول الترابط بين Qubit 2 وQubit 1. تُكتب قيم التوقع كهذه أيضًا على النحو Z2Z1\langle Z_2 Z_1 \rangle.

إذا كانت الحالة المرصودة هي حالة GHZ الثلاثية Qubits، فيجب أن يساوي قياس Z2Z1\langle Z_2 Z_1 \rangle القيمة 1.

2.2 تحسين الدوائر والمؤثرات

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

تُنشئ الشفرة التالية جهازًا حقيقيًا لإرسال مهمة إليه، وتحوّل Circuit والمقادير الرصدية لتتوافق مع ISA ذلك Backend. إذا لم تحفظ بيانات اعتمادك مسبقًا، اتبع التعليمات هنا للمصادقة باستخدام رمز API الخاص بك.

# Choose a real backend
service = QiskitRuntimeService(channel='ibm_quantum_platform',)
backend = service.least_busy(min_num_qubits=156)
# print backend details
print(
f"Name: {backend.name}\n"
f"Version: {backend.backend_version}\n"
f"No. of qubits: {backend.num_qubits}\n"
f"Processor type: {backend.processor_type}\n"
)
Name: ibm_marrakesh
Version: 1.0.21
No. of qubits: 156
Processor type: {'family': 'Heron', 'revision': '2'}
# option to use the AerSimulator instead of a real quantum device
seed_sim=42
backend=AerSimulator.from_backend(backend,seed_simulator=seed_sim)

تحويل Circuit إلى دائرة ISA

# Convert to an ISA circuit and layout-mapped observables.

pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc)

isa_circuit.draw("mpl", idle_wires=False)

Quantum circuit diagram

mapped_observables = [
observable.apply_layout(isa_circuit.layout) for observable in observables
]
print(mapped_observables)
[SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIXIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])]

2.3 التنفيذ باستخدام Primitives الكمومية

يمكن للحواسيب الكمومية أن تُنتج نتائج عشوائية، لذا تجمع عادةً عينة من المخرجات بتشغيل Circuit مرات عديدة. يمكنك تقدير قيمة المقدار الرصدي باستخدام صنف Estimator. يُعدّ Estimator واحدًا من اثنين من primitives؛ الآخر هو Sampler الذي يمكن استخدامه للحصول على بيانات من حاسوب كمومي. تمتلك هذه الكائنات أسلوب run() الذي ينفّذ مجموعة الدوائر والمقادير الرصدية والمعاملات (إن وُجدت)، باستخدام كتلة PUB الموحدة (PUB). عند تشغيل هذه الشفرة على عتاد كمومي حقيقي، ضع في اعتبارك تطبيق تقنيات تخفيف الأخطاء وإزالتها لتقليل الضوضاء الجوهرية للحاسوب الكمومي.

# Construct the Estimator instance.
estimator = Estimator(mode=backend)
estimator.options.resilience_level = 1
estimator.options.default_shots = 5000

إرسال مهمة باستخدام Primitive الـ Estimator.

# One pub, with one circuit to run against six different observables.
job = estimator.run([(isa_circuit, mapped_observables)])

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job.job_id()}")
>>> Job ID: 97ecd036-1767-49b0-a1dc-c71638c3c3c4
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

بعد إرسال المهمة، يمكنك الانتظار حتى اكتمالها ضمن نسخة Python الحالية، أو استخدام job_id لاسترداد البيانات لاحقًا. (راجع قسم استرداد المهام للتفاصيل.)

بعد اكتمال المهمة، افحص مخرجاتها من خلال الخاصية result() للمهمة.

# This is the result of the entire submission.  You submitted one Pub,
# so this contains one inner result (and some metadata of its own).
job_result = job.result()

# This is the result from our single pub, which had six observables,
# so contains information on all six.
pub_result = job.result()[0]

يمكننا الآن تنفيذ Circuit باستخدام Primitive الـ Sampler أيضًا

# We include the measurements in the circuit
qc.measure_all()
sampler = Sampler(mode=backend)
qc.draw(output="mpl")

Quantum circuit diagram

إرسال مهمة باستخدام Primitive الـ Sampler.

job_sampler = sampler.run(pm.run([qc]))

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job_sampler.job_id()}")
# Get the results
results_sampler = job_sampler.result()
>>> Job ID: a6ee4d2f-c80d-4a86-9a76-e4b1a74502e7

2.4 تحليل النتائج

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

يمكن الوصول إلى قيم التوقع والانحرافات المعيارية للمقادير الرصدية التي حددتها لـ Estimator من خلال الخاصيتين PubResult.data.evs وPubResult.data.stds لنتيجة المهمة. للحصول على النتائج من Sampler، استخدم الدالة PubResult.data.meas.get_counts()، التي ستُرجع قاموسًا dict من القياسات على شكل سلاسل بتية كمفاتيح وأعداد القياسات كقيم مقابلة لها. لمزيد من المعلومات، راجع البدء مع Sampler.

# Plot the result
from matplotlib import pyplot as plt
values = pub_result.data.evs
errors = pub_result.data.stds
# plotting graph
# Plotting with error bars
plt.errorbar(observables_labels, values, yerr=errors, fmt='-o', capsize=5)
plt.xlabel("Observables")
plt.ylabel("Values")
plt.title("Plot of Observables vs Values with Error Bars")
plt.grid(True)
plt.tight_layout()
plt.show()

Plot output

نلاحظ أن المقدارين الرصديين ZZIZZI وIIIIII لهما قيمة توقع تساوي 1، إذ يُدخل ZZIZZI إشارتين سالبتين تتلاشيان، بينما يعمل IIIIII كمحايد لا يغير حالة GHZ. أما بقية المقادير الرصدية فلها قيمة توقع تساوي 0، لأن مؤثرات ZZ فيها تُدخل عددًا فرديًا من الإشارات السالبة، أو لأن مؤثرات XX تقلب عددًا من Qubits يجعل الحالات المتداخلة متعامدة.

نرسم الآن نتائج الـ Sampler

counts_list = results_sampler[0].data.meas.get_counts()
print(counts_list)
print(f"Outcomes : {counts_list}")
display(plot_histogram(counts_list, title="GHZ state"))
{'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}
Outcomes : {'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}

Code output

2.5 التوسع إلى أعداد كبيرة من Qubits

في الحوسبة الكمومية، يُعدّ العمل على نطاق الفائدة أمرًا بالغ الأهمية لتحقيق تقدم في هذا المجال. يتطلب ذلك إجراء حسابات على نطاق أوسع بكثير؛ مع دوائر قد تستخدم أكثر من 100 Qubit وأكثر من 1000 Gate. يخطو هذا المثال خطوة صغيرة في هذا الاتجاه بتوسيع نطاق مسألة GHZ إلى n=10n=10 Qubits. يستخدم سير عمل Qiskit patterns وينتهي بقياس قيمة التوقع Z0Zi\langle Z_0 Z_i \rangle .

الخطوة 1. تعيين المشكلة

اكتب دالة تُرجع QuantumCircuit تُهيئ حالة GHZ ذات nn Qubits (وهي بالأساس حالة بيل موسّعة)، ثم استخدم تلك الدالة لإعداد حالة GHZ من 10 Qubits وجمع المقادير الرصدية المراد قياسها.

def get_qc_for_n_qubit_GHZ_state(n: int) -> QuantumCircuit:

qc = QuantumCircuit(n)
qc.h(0)
for i in range(n-1):
qc.cx(i, i+1)
return qc
n = 10
qc_n_GHZ = get_qc_for_n_qubit_GHZ_state(n)
qc_n_GHZ.draw("mpl")

Quantum circuit diagram

بعد ذلك، عيّن إلى المؤثرات ذات الاهتمام. يستخدم هذا المثال مؤثرات ZZ بين Qubits لدراسة السلوك عند ازدياد المسافة بينها. ستكشف قيم التوقع غير الدقيقة (الفاسدة) بين Qubits البعيدة عن مستوى الضوضاء الموجودة.

# ZZII...II, ZIZI...II, ... , ZIII...IZ
operator_strings = [
"Z" + i * "I" + "Z" + "I" * (n-i-2) for i in range(n-1)
]
print(operator_strings)
print(len(operator_strings))

operators = [SparsePauliOp(operator) for operator in operator_strings]
['ZZIIIIIIII', 'ZIZIIIIIII', 'ZIIZIIIIII', 'ZIIIZIIIII', 'ZIIIIZIIII', 'ZIIIIIZIII', 'ZIIIIIIZII', 'ZIIIIIIIZI', 'ZIIIIIIIIZ']
9

الخطوة 2. تحسين المشكلة للتنفيذ على Backend الكمومي

حوّل Circuit والمقادير الرصدية لتتوافق مع ISA الخاص بـ Backend.

# Convert to an ISA circuit and layout-mapped observables.
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc_n_GHZ)
isa_operators_list = [operator.apply_layout(isa_circuit.layout) for operator in operators]

الخطوة 3. التنفيذ على Backend

أرسل المهمة وإذا نفّذتها على عتاد حقيقي فعّل إزالة الأخطاء باستخدام تقنية تُعرف بـ الفصل الديناميكي (dynamical decoupling). يحدد مستوى المرونة مقدار المقاومة المبنية ضد الأخطاء. تُولّد المستويات الأعلى نتائج أكثر دقة، لكن على حساب وقت معالجة أطول. لمزيد من الشرح حول الخيارات المحددة في الشفرة التالية، راجع تكوين تخفيف الأخطاء لـ Qiskit Runtime.

# Submit the circuit to Estimator
job = estimator.run([(isa_circuit, isa_operators_list)])
job_id = job.job_id()
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

الخطوة 4. معالجة النتائج لاحقًا

لفهم سلوك الحالات الكمومية المتشابكة على العتاد الحقيقي، نحلل الترابطات الزوجية بين Qubits في الأساس Z. نُركّز تحديدًا على قيم التوقع ⟨Z₀Zᵢ⟩ التي تقيس مدى الترابط بين Qubit 0 وكل Qubit i آخر. سنرسم تحديدًا:

ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle

ما القيم التي تتوقع رؤيتها في الرسم البياني لـ ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle ؟

الخيارات:

أ) تناقصية كلما زاد ii

ب) ثابتة عند 1

ج) انحرافات صغيرة حول 1

د) تتناوب بين 1 و0 لقيم ii الفردية والزوجية

data = list(range(1, len(operators) + 1))  # Distance between the Z operators
result = job.result()[0]
values = result.data.evs # Expectation value at each Z operator.
values = [
v / values[0] for v in values
] # Normalize the expectation values to evaluate how they decay with distance.

plt.plot(data, values, marker="o", label=f"{n}-qubit GHZ state")
plt.xlabel("Distance between qubits $i$")
plt.ylabel(r"$\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle $")
plt.legend()
plt.show()

Plot output

في هذا الرسم البياني نلاحظ أن Z0Zi\langle Z_0 Z_i \rangle يتذبذب حول القيمة 1، على الرغم من أنه في المحاكاة المثالية يجب أن يساوي جميع Z0Zi\langle Z_0 Z_i \rangle القيمة 1.

كما ترى، نتائج تجارب الـ 10 Qubits جيدة لكن لا تزال تحتوي على بعض الأخطاء. إحدى طرق تحسين النتائج هي تنفيذ حالة GHZ بكفاءة أعلى.

عادةً ما تُنفَّذ حالة GHZ بتسلسل متدرج من بوابات CNOT. غير أنه يمكن تنفيذ حالة GHZ بكفاءة أكبر، بتقليل العمق ثنائي Qubits من n إلى n/2 أو أقل.

أحد المقاييس الهامة لمعرفة مدى دقة النتائج أو مدى انخفاض الضوضاء في Circuit هو عمق البوابات ثنائية Qubits. ذلك لأن معدلات الخطأ للبوابات ثنائية Qubits (أعلى بـ ~10 أضعاف من البوابات أحادية Qubit) تهيمن على أخطاء الدائرة بأكملها. استخدم الشفرة التالية للحصول على عمق البوابات ثنائية Qubits لـ Circuit.

qc.depth(lambda x: x.operation.num_qubits == 2)
def better_ghz(n):
"fan out"
s = int(n / 2)
qc = QuantumCircuit(n)
qc.h(s)
for m in range(s, 0, -1):
qc.cx(m, m - 1)
if not (n % 2 == 0 and m == s):
qc.cx(n - m - 1, n - m)
return qc

better_ghz(n).draw("mpl")

Quantum circuit diagram

# Check 2-qubit gate depth before transpilation
qc_better_ghz = better_ghz(n)
qc_better_ghz.depth(lambda x: x.operation.num_qubits == 2)
5

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

3. ترميز المعلومات

3.1 الترميز بالسعة

بعد أن رأينا كيفية بناء الدوائر الكمومية، يُثير الاهتمامَ استكشافُ كيفية ترميز المعلومات الكلاسيكية في الحالات الكمومية. إحدى الطرق القوية هي الترميز بالسعة، حيث تمثل سعات (amplitudes) الحالة الكمومية مكونات متجه كلاسيكي.

لنأخذ مثالًا بسيطًا. افترض أننا نريد ترميز المتجه الكلاسيكي

x=[x0x1x2x3]\vec{x} = \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ x_3 \end{bmatrix}

في حالة كمومية من Qubitين. الهدف هو إعداد الحالة الكمومية:

ψ=x000+x101+x210+x311\ket{\psi} = x_0\ket{00} + x_1\ket{01} + x_2\ket{10} + x_3\ket{11}

حيث x0,x1,x2,x3Rx_0, x_1, x_2, x_3 \in \mathbb{R} (أو C\mathbb{C}) والمتجه منظَّم بحيث:

x02+x12+x22+x32=1|x_0|^2 + |x_1|^2 + |x_2|^2 + |x_3|^2 = 1

نأخذ الآن المثال المحدد: x=[0.8924,0.3696,0.2391,0.0990]\vec{x} = [0.8924, 0.3696, 0.2391, 0.0990]

وعندئذٍ تكون الحالة الكمومية المقابلة:

ψ=0.892400+0.369601+0.239110+0.099011\begin{aligned} \ket{\psi} &= 0.8924\,\ket{00} + 0.3696\,\ket{01} + 0.2391\,\ket{10} + 0.0990\,\ket{11} \end{aligned}

يمكن تحضير هذه الحالة باستخدام مجموعة من بوابات الدوران RyR_y بزوايا π/6\pi/6 وπ/4\pi/4 للـ Qubit 0 والـ Qubit 1 على التوالي

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
import numpy as np

qc = QuantumCircuit(2)

qc.ry(np.pi / 6, 0)
qc.ry(np.pi / 4, 1)

simulator = AerSimulator()
qc.save_statevector()
result = simulator.run(qc).result()
statevector = result.get_statevector()

print("Statevector:", statevector)
qc.draw(output="mpl")
Statevector: Statevector([0.8923991 +0.j, 0.23911762+0.j, 0.36964381+0.j,
0.09904576+0.j],
dims=(2, 2))

Quantum circuit diagram

from qiskit.quantum_info import Statevector

# Define our vector
v = np.array([0.8924, 0.3696, 0.2391, 0.0990])
v = v/np.linalg.norm(v)
# Create a statevector from the vector
state = Statevector(v)

# Initialize a quantum circuit with 2 qubits
qc = QuantumCircuit(2)
qc.initialize(state.data, [0, 1])

# Optional: simulate the state
print("Statevector:", state)

# Visualize the circuit
qc.decompose().decompose().decompose().decompose().decompose().draw("mpl")
Statevector: Statevector([0.89242154+0.j, 0.36960892+0.j, 0.23910577+0.j,
0.09900239+0.j],
dims=(2, 2))

Quantum circuit diagram

وهكذا رأينا كيفية ترميز المعلومات باستخدام بوابات الدوران.

3.2 الترميز بالزوايا والدوائر المُعلْمَنة

من أساليب ترميز المعلومات في الحاسوب الكمومي التي تثير الاهتمام بشكل خاص، تصميم Circuit كمومية تحتوي على بعض زوايا الدوران θ\vec{\theta} أو المعاملات القابلة للضبط بهدف تمثيل مجموعة من الدوال f(θ)f(\vec{\theta}). لنأخذ على سبيل المثال الـ Circuit الكمومية المُعلْمَنة التالية:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta = Parameter("θ")

qc = QuantumCircuit(2)
# We applied a parametrized RX gate
qc.rx(theta, 0)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

رياضياً، يمكننا تحليل مجموعة الدوال التي يمكن تمثيلها بهذه الـ Circuit:

CNOT01Rx{0}(θ)00=CNOT01(cos(θ/2)00isin(θ/2)10)=cos(θ/2)00isin(θ/2)11\text{CNOT}_{01} \, R_x^{\{0\}}(\theta) |00\rangle = \text{CNOT}_{01} \left( \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{10} \right) = \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{11}

من الواضح تماماً أن عدد الحالات التي يمكن تمثيلها بهذه الـ Circuit الكمومية محدود، إذ لا يمكننا تمثيل الحالتين 10\ket{10} أو 01\ket{01} على سبيل المثال. غير أن مجموعة الحالات القابلة للتمثيل تتوسع حين ندخل مزيداً من عمليات الدوران في المواضع المناسبة:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta1 = Parameter("θ1")
theta2 = Parameter("θ2")

qc = QuantumCircuit(2)
qc.rx(theta1, 0)
qc.rx(theta2, 1)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

في هذه الحالة، تكون الحالات الكمومية التي سنمثلها هي:

\begin{align*} \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2) R_x^{\{0}}(\theta_1) \ket{00} &= \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2)\left( \cos(\theta_1/2)\ket{00} - i\sin(\theta_1/2)\ket{10} \right) \\ &= \text{CNOT}_{01}\left( \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \right. \\ &\quad \left. - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{10} + \sin(\theta_1/2)\sin(\theta_2/2)\ket{11} \right) \\ &= \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \\ &\quad + \sin(\theta_1/2)\sin(\theta_2/2)\ket{10} - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{11} \end{align*}

يتضح أن هذه الـ Circuit تولّد مجموعة أوسع من الحالات الكمومية مقارنةً بالسابقة؛ إذ باتت قادرة على إنتاج حالات ذات سعات غير صفرية للحالتين 01\ket{01} و10\ket{10} اللتين لم تكن ممكنتين مع الـ Circuit السابقة. بيد أن هذه الـ Circuit لا تزال ليست مولِّداً شاملاً للحالات الكمومية، وإن كانت ذات قدرة تعبيرية كافية لتصميم دوائر تتيح قدراً من المرونة في تمثيل دوال معينة. وبوجه عام، كلما أدخلنا مزيداً من المعاملات المستقلة (الزوايا)، ازدادت القدرة التعبيرية للـ Circuit على تقريب الحالات الكمومية الاعتباطية.

الأنزاتس ومكتبة الدوائر

يمكن استخدام هذا النوع من الـ Circuit الكمومية المُعلْمَنة لبناء الأنزاتس، وهي حالات كمومية تجريبية تسعى إلى تقريب حل مسألة ما. وتُعدّ هذه الأنزاتس مكوناً محورياً في الخوارزميات الكمومية التغايرية، وهي فئة من الخوارزميات الهجينة الكمومية-الكلاسيكية التي تستخدم الحاسوب الكمومي لتقييم دالة تكلفة وتستخدم مُحسِّناً كلاسيكياً لتصغيرها. سنتناول هذه الموضوعات بالتفصيل في وحدة لاحقة، لكننا سنوضح الآن كيفية بناء أنزاتس بسيطة باستخدام مكتبة Circuit في Qiskit.

from qiskit.circuit.library import efficient_su2

SU2_ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
SU2_ansatz.decompose().draw(output="mpl")

Quantum circuit diagram

رأينا كيفية بناء أنزاتس بسيطة باستخدام الدالة efficient_su2 من qiskit.circuit.library، والتي ستكون قادرة على توليد طيف واسع من الحالات الكمومية عبر ضبط معاملاتها θ\vec{\theta}.

الخاتمة

في هذا الدفتر، تعلمت كيفية بناء الـ Circuit الكمومية، بدءاً من تركيب الـ Gate الكمومية وصولاً إلى تعريف المقاسات والقيام بقياسها، وكيفية تنفيذ هذه الـ Circuit بكفاءة على المحاكيات والأجهزة الكمومية الفعلية. كما اطلعت على أهمية التصميم الدقيق للـ Circuit للتقليل من الأخطاء عند العمل مع الأجهزة الكمومية الحقيقية، فضلاً عن استراتيجيات توسيع نطاق الـ Circuit لتشمل أعداداً أكبر من الـ Qubit، ولا سيما من خلال مثال حالة GHZ. علاوة على ذلك، استكشفت تقنيات مختلفة لترميز المعلومات الكلاسيكية في الحالات الكمومية، بما فيها الترميز بالسعة والترميز بالزاوية. بكل هذا، أصبحت مستعداً تماماً للانتقال إلى الجلسة التالية والبدء في العمل مع الخوارزميات الكمومية.

تثبيت Qiskit Code Assistant في VSCode

انقر على الرابط واتبع التعليمات.

مسألة إضافية: الإرسال الكمومي عن بُعد

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

يتيح الإرسال الكمومي عن بُعد نقل الحالة الكمومية لـ Qubit من موقع إلى آخر. وعلى الرغم من أن هذا النقل يبدو فورياً، فإنه لا ينتهك قوانين الفيزياء. كيف يكون ذلك ممكناً؟ دعونا نتعمق في الأمر!

الإرسال الكمومي عن بُعد هو بروتوكول يسمح لمرسل (أليس) بنقل الحالة ψ|\psi\rangle لـ Qubit q إلى مستقبِل (بوب) باستخدام مصدرَين رئيسيَّين: زوج متشابك مشترك من الـ Qubit a وb، وبِتَّي اتصال كلاسيكي c0 وc1.

ما يحتاجه البروتوكول أساساً هو:

  • q: الـ Qubit الخاص بأليس، في الحالة الابتدائية ψ|\psi\rangle التي نريد إرسالها.
  • a: نصيب أليس من الزوج المتشابك المشترك.
  • b: نصيب بوب من الزوج المتشابك المشترك.
  • c0، c1: بِتَّات كلاسيكية لتخزين نتائج قياسات أليس.

وكيف يعمل ذلك؟ سير العمل على النحو التالي:

  1. تحضير حالة أليس ψ|\psi\rangle على q. سنُنشئ حالة محددة كـ +|+\rangle للتحقق.
  2. إنشاء التشابك: توليد زوج بيل بين a وb.
  3. عمليات أليس: تُجري أليس "قياس بيل" على Qubitيها (q وa) وتخزّن النتائج الكلاسيكية في c0 وc1.
  4. الاتصال الكلاسيكي: ترسل أليس بِتَّيها الكلاسيكيَّين (c0، c1) إلى بوب.
  5. تصحيحات بوب: يُطبّق بوب بوابات كمومية محددة (X و/أو Z) على Qubitه (b)، مشروطةً بقيمتَي c0 وc1 اللتين تلقّاهما.

إذا تمت جميع الخطوات بصورة صحيحة، سينتهي Qubit بوب b في الحالة ψ|\psi\rangle، أي الحالة الأصلية لـ q الخاص بأليس!

للاطلاع على شرح أعمق واستكشاف أوسع للإرسال الكمومي عن بُعد، بما يشمل التفسير الرياضي لسبب نجاح هذا البروتوكول، يمكنك الرجوع إلى موارد IBM Quantum Learning: الإرسال الكمومي عن بُعد. وهذا جزء من دورة أساسيات المعلومات الكمومية.


import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, plot_bloch_multivector

# Define individual quantum registers for each qubit
q = QuantumRegister(1, name='q') # message qubit
a = QuantumRegister(1, name='a') # Alice's entangled qubit
b = QuantumRegister(1, name='b') # Bob's entangled qubit

# Classical register for Alice's measurements
cr_alice = ClassicalRegister(2, name='c_alice')

# Create quantum circuit
teleport_qc = QuantumCircuit(q, a, b, cr_alice, name='Teleportation')

# Step 1: Prepare message state |+⟩ on q
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 2: Create entanglement between a and b
teleport_qc.h(a[0])
teleport_qc.cx(a[0], b[0])
teleport_qc.barrier()

# Step 3: Alice's Bell measurement
teleport_qc.cx(q[0], a[0])
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 4: Alice measures q and a
teleport_qc.measure(q[0], cr_alice[0])
teleport_qc.measure(a[0], cr_alice[1])
teleport_qc.barrier()

# Step 5: Bob's conditional measurements
with teleport_qc.if_test((cr_alice[1], 1)):
teleport_qc.x(b[0])
with teleport_qc.if_test((cr_alice[0], 1)):
teleport_qc.z(b[0])

# Draw the circuit
teleport_qc.draw(output='mpl')

Quantum circuit diagram

بعد تنفيذ البروتوكول، يطرح نفسه سؤال جوهري: كيف نتحقق من نجاح عملية الإرسال؟ لا يمكننا "رؤية" حالة Qubit بوب مباشرةً بعد البروتوكول. غير أننا بما أننا حضّرنا الحالة الابتدائية لأليس ψ|\psi\rangle (اخترنا +|+\rangle)، يمكننا استخدام نوع خاص من المحاكاة للتحقق من أن Qubit بوب b انتهى في تلك الحالة ذاتها.

سنستخدم AerSimulator مع save_statevector للتحقق من أن Qubit بوب b ينتهي في الحالة الأصلية لأليس (+|+\rangle). يحسب هذا المحاكي متجه الحالة الكمومية النهائي، ثم يمثّله باستخدام plot_bloch_multivector لتصوير Qubit بوب (b) مقارنةً بالحالة الابتدائية لأليس (q).

# Simulate the teleportation circuit
sv_simulator = AerSimulator(method='statevector')
teleport_qc_sv = teleport_qc.copy()
teleport_qc_sv.save_statevector()

# Execute the circuit on the statevector simulator
job_sv = sv_simulator.run(teleport_qc_sv)
result_sv = job_sv.result()

# Get the final statevector
final_statevector = result_sv.get_statevector()
print("Visualizing final qubit states:")
display(plot_bloch_multivector(final_statevector))
print("Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.")
Visualizing final qubit states:

Quantum circuit diagram

Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.

كما يتضح من التصوير، انهار أول Qubitين (التابعان لأليس) إلى 0 أو 1. في المقابل، يشير الـ Qubit الثالث (التابع لبوب) والمُمثَّل في كرة بلوخ الثالثة على طول المحور السيني، مما يدل على أنه في الحالة +|+\rangle؛ وهكذا نكون قد نفّذنا بروتوكول الإرسال الكمومي عن بُعد بنجاح!

ملخص

في هذه المرحلة، من المفيد تقديم ملخص سريع لما أنجزناه:

  • نقلت أليس حالة كمومية مجهولة إلى بوب.
  • لم يُنقل أي جسيم مادي.
  • الحالة الأصلية على Qubit أليس تُدمَّر، وهو ما يتسق مع نظرية عدم الاستنساخ.

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