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

تطبيق Qiskit

في هذا القسم، سنلقي نظرة على بعض تطبيقات Qiskit للمفاهيم المقدَّمة في هذا الدرس. إذا أردتَ تشغيل هذه التطبيقات بنفسك، وهو أمر نشجعك عليه بشدة، راجع صفحة تثبيت Qiskit في توثيق IBM Quantum للتفاصيل حول طريقة إعداد Qiskit.

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

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import __version__

print(__version__)
2.1.1

إذا كنت تُشغِّل هذا في بيئة Python سحابية، قد تحتاج إلى تثبيت بعض الحزم التالية:

#!pip install qiskit
#!pip install jupyter
#!pip install sympy
#!pip install matplotlib
#!pip install pylatexenc

المتجهات والمصفوفات في Python

يستخدم Qiskit لغة البرمجة Python، لذا قبل الحديث عن Qiskit تحديدًا، قد يكون من المفيد استعراض موجز لحسابات المصفوفات والمتجهات في Python.

في Python، يمكن إجراء حسابات المصفوفات والمتجهات باستخدام صنف array من مكتبة NumPy، التي توفر وظائف لعدد كبير من الحسابات العددية والعلمية. الكود التالي يحمِّل هذه المكتبة، ويعرِّف متجهين عموديين ket0 وket1 يقابلان متجهَي الحالة الكمومية للكيوبت 0\vert 0\rangle و1\vert 1\rangle، ثم يطبع متوسطهما.

import numpy as np

ket0 = np.array([[1], [0]])
ket1 = np.array([[0], [1]])

print(ket0 / 2 + ket1 / 2)
[[0.5]
[0.5]]

يمكننا أيضًا استخدام array لإنشاء مصفوفات تمثل عمليات.

M1 = np.array([[1, 1], [0, 0]])
M2 = np.array([[1, 0], [0, 1]])
M = M1 / 2 + M2 / 2
print(M)
[[1.  0.5]
[0. 0.5]]

يُرجى ملاحظة أن جميع الكود الموجود ضمن درس معين في هذه الدورة يُفترض تشغيله بالتسلسل. لذلك، لا داعي لاستيراد NumPy مرة أخرى هنا، إذ سبق استيراده.

ضرب المصفوفات، بما يشمل ضرب المصفوفة بمتجه كحالة خاصة، يمكن إجراؤه باستخدام دالة matmul من NumPy.

print(np.matmul(M1, ket1))
print(np.matmul(M1, M2))
print(np.matmul(M, M))
[[1]
[0]]
[[1 1]
[0 0]]
[[1. 0.75]
[0. 0.25]]

هذا التنسيق في المخرجات يترك الكثير مما يُرغب فيه بصريًا. أحد الحلول، للحالات التي تستدعي مظهرًا أجمل، هو استخدام دالة array_to_latex في Qiskit من وحدة qiskit.visualization. لاحظ أنه في الكود الذي يلي، نستخدم دالة Python العامة display. في المقابل، قد يختلف السلوك المحدد لـprint بحسب ما يُطبع، كما هو الحال مع المصفوفات المعرَّفة بـNumPy.

from qiskit.visualization import array_to_latex

display(array_to_latex(np.matmul(M1, ket1)))
display(array_to_latex(np.matmul(M1, M2)))
display(array_to_latex(np.matmul(M, M)))
[10] \begin{bmatrix} 1 \\ 0 \\ \end{bmatrix} [1100] \begin{bmatrix} 1 & 1 \\ 0 & 0 \\ \end{bmatrix} [134014] \begin{bmatrix} 1 & \frac{3}{4} \\ 0 & \frac{1}{4} \\ \end{bmatrix}

الحالات والقياسات والعمليات

يتضمن Qiskit عدة أصناف تتيح إنشاء الحالات والقياسات والعمليات والتعامل معها — لذا لا حاجة لبرمجة كل ما يلزم لمحاكاة الحالات الكمومية والقياسات والعمليات في Python. فيما يلي بعض الأمثلة التي ستساعدك على البدء.

تعريف متجهات الحالة وعرضها

يوفر صنف Statevector في Qiskit وظائف لتعريف متجهات الحالة الكمومية والتعامل معها. في الكود الذي يلي، يُستورَد صنف Statevector ويُعرَّف عدة متجهات. (نستورد أيضًا دالة sqrt من مكتبة NumPy لحساب الجذر التربيعي. يمكن بدلًا من ذلك استدعاؤها كـnp.sqrt شريطة أن تكون NumPy مستوردة بالفعل كما فعلنا أعلاه؛ هذه مجرد طريقة مختلفة لاستيراد هذه الدالة بعينها واستخدامها.)

from qiskit.quantum_info import Statevector
from numpy import sqrt

u = Statevector([1 / sqrt(2), 1 / sqrt(2)])
v = Statevector([(1 + 2.0j) / 3, -2 / 3])
w = Statevector([1 / 3, 2 / 3])

يتضمن صنف Statevector طريقة draw لعرض متجهات الحالة بأشكال متنوعة، منها text للنص العادي، وlatex لـLaTeX المُصيَّر، وlatex_source لكود LaTeX، وهو مفيد للنسخ واللصق في المستندات. (استخدم print بدلًا من display لعرض كود LaTeX بشكل أفضل.)

display(u.draw("text"))
display(u.draw("latex"))
print(u.draw("latex_source"))
[0.70710678+0.j,0.70710678+0.j]

220+221\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle

يتضمن صنف Statevector أيضًا طريقة is_valid التي تتحقق مما إذا كان متجه معيَّن متجهَ حالة كمومية صحيحًا (أي أن معياره الإقليدي يساوي 1):

display(u.is_valid())
display(w.is_valid())
True
False

محاكاة القياسات باستخدام Statevector

بعد ذلك سنرى إحدى طرق محاكاة قياسات الحالات الكمومية في Qiskit باستخدام طريقة measure من صنف Statevector. لنستخدم نفس متجه الحالة الكمومية للكيوبت v المعرَّف سابقًا.

display(v.draw("latex"))

(13+2i3)0231(\frac{1}{3} + \frac{2 i}{3}) |0\rangle- \frac{2}{3} |1\rangle

تشغيل طريقة measure يحاكي قياسًا بالأساس القياسي. تُعيد هذه الطريقة نتيجة القياس، بالإضافة إلى متجه الحالة الكمومية الجديد للنظام بعد القياس. (نستخدم هنا دالة Python print مع بادئة f للطباعة المنسَّقة مع تعابير مُضمَّنة.)

outcome, state = v.measure()
print(f"Measured: {outcome}\nPost-measurement state:")
display(state.draw("latex"))
Measured: 1
Post-measurement state:

1- |1\rangle

نتائج القياس احتمالية، لذا قد تُعيد هذه الطريقة نتائج مختلفة عند تشغيلها عدة مرات. لمثال المتجه v المعرَّف أعلاه، تُعرِّف طريقة measure متجه الحالة الكمومية بعد القياس على أنه

(1+2i5)0\biggl(\frac{1 + 2i}{\sqrt{5}}\biggr) \vert 0\rangle

(بدلًا من 0\vert 0\rangle) أو

1- \vert 1\rangle

(بدلًا من 1\vert 1\rangle) اعتمادًا على نتيجة القياس. في كلتا الحالتين، البدائل لـ0\vert 0\rangle و1\vert 1\rangle مكافئة في الواقع لمتجهي الحالة هذين؛ يُقال إنهما متكافئان حتى طور عام لأن أحدهما يساوي الآخر مضروبًا في عدد مركب على الدائرة الوحدوية. تناقَش هذه المسألة بمزيد من التفصيل في درس الدوائر الكمومية، ويمكن تجاهلها في الوقت الحالي.

سيُصدر Statevector خطأً إذا طُبِّقت طريقة measure على متجه حالة كمومية غير صحيح.

يأتي Statevector أيضًا مع طريقة sample_counts تتيح محاكاة أي عدد من القياسات على النظام، كل مرة نبدأ بنسخة جديدة من الحالة. على سبيل المثال، يعرض الكود التالي نتيجة قياس المتجه v 10001000 مرة، مما يؤدي (باحتمالية كبيرة) إلى الحصول على النتيجة 00 تقريبًا 55 من كل 99 مرات (أو حوالي 556556 من 10001000 تجربة) والنتيجة 11 تقريبًا 44 من كل 99 مرات (أو حوالي 444444 من 10001000). يُوضِّح الكود التالي أيضًا دالة plot_histogram من وحدة qiskit.visualization لتصوير النتائج.

from qiskit.visualization import plot_histogram

statistics = v.sample_counts(1000)
plot_histogram(statistics)

Output of the previous code cell

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

تنفيذ العمليات باستخدام Operator وStatevector

يمكن تعريف العمليات الوحدوية في Qiskit باستخدام صنف Operator، كما في المثال التالي. يتضمن هذا الصنف طريقة draw بحجج مشابهة لتلك الخاصة بـStatevector. لاحظ أن خيار latex يُنتج نتائج مكافئة لـarray_from_latex.

from qiskit.quantum_info import Operator

Y = Operator([[0, -1.0j], [1.0j, 0]])
H = Operator([[1 / sqrt(2), 1 / sqrt(2)], [1 / sqrt(2), -1 / sqrt(2)]])
S = Operator([[1, 0], [0, 1.0j]])
T = Operator([[1, 0], [0, (1 + 1.0j) / sqrt(2)]])

display(T.draw("latex"))
[10022+2i2] \begin{bmatrix} 1 & 0 \\ 0 & \frac{\sqrt{2}}{2} + \frac{\sqrt{2} i}{2} \\ \end{bmatrix}

يمكننا تطبيق عملية وحدوية على متجه حالة باستخدام طريقة evolve.

v = Statevector([1, 0])

v = v.evolve(H)
v = v.evolve(T)
v = v.evolve(H)
v = v.evolve(S)
v = v.evolve(Y)

display(v.draw("latex"))

(0.14644660940.3535533906i)0+(0.3535533906+0.8535533906i)1(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

معاينة للدوائر الكمومية

لن تُقدَّم الدوائر الكمومية رسميًا حتى درس الدوائر الكمومية، وهو الدرس الثالث في هذه الدورة، لكن يمكننا مع ذلك تجريب تركيب عمليات الكيوبت الوحدوية باستخدام صنف QuantumCircuit في Qiskit. على وجه التحديد، يمكننا تعريف دائرة كمومية (وهي في هذه الحالة مجرد تسلسل من العمليات الوحدوية على كيوبت واحد) على النحو التالي.

from qiskit import QuantumCircuit

circuit = QuantumCircuit(1)

circuit.h(0)
circuit.t(0)
circuit.h(0)
circuit.s(0)
circuit.y(0)

display(circuit.draw(output="mpl"))

Output of the previous code cell

نستخدم هنا طريقة draw من صنف QuantumCircuit مع المُصيِّر mpl (اختصار لـMatplotlib، وهي مكتبة Python للتصوير). هذا هو المُصيِّر الوحيد الذي سنستخدمه للدوائر الكمومية في هذه الدورة، لكن توجد خيارات أخرى تشمل مُصيِّرًا نصيًا وآخر مبنيًا على LaTeX.

تُطبَّق العمليات بالتسلسل، من اليسار إلى اليمين في المخطط. طريقة مريحة للحصول على المصفوفة الوحدوية المقابلة لهذه الدائرة هي استخدام طريقة from_circuit من صنف Operator.

display(Operator.from_circuit(circuit).draw("latex"))
[0.14644660940.3535533906i0.8535533906+0.3535533906i0.3535533906+0.8535533906i0.3535533906+0.1464466094i] \begin{bmatrix} 0.1464466094 - 0.3535533906 i & 0.8535533906 + 0.3535533906 i \\ -0.3535533906 + 0.8535533906 i & 0.3535533906 + 0.1464466094 i \\ \end{bmatrix}

يمكننا أيضًا تهيئة متجه حالة كمومية ابتدائي ثم تطور تلك الحالة وفق تسلسل العمليات الذي تصفه الدائرة.

ket0 = Statevector([1, 0])
v = ket0.evolve(circuit)
display(v.draw("latex"))

(0.14644660940.3535533906i)0+(0.3535533906+0.8535533906i)1(0.1464466094 - 0.3535533906 i) |0\rangle+(-0.3535533906 + 0.8535533906 i) |1\rangle

الكود التالي يحاكي تجربة يُقاس فيها الحالة الناتجة من الدائرة أعلاه بقياس أساس قياسي 4000 مرة (باستخدام نسخة جديدة من الحالة في كل مرة).

statistics = v.sample_counts(4000)
display(plot_histogram(statistics))

Output of the previous code cell