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

خوارزمية غروفر

في هذه الوحدة من Qiskit in Classrooms، يجب أن يكون لدى الطلاب بيئة Python تعمل بشكل صحيح مع تثبيت الحزم التالية:

  • qiskit الإصدار v2.1.0 أو أحدث
  • qiskit-ibm-runtime الإصدار v0.40.1 أو أحدث
  • qiskit-aer الإصدار v0.17.0 أو أحدث
  • qiskit.visualization
  • numpy
  • pylatexenc

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

تم اختبار هذه الوحدة واستغرقت 12 ثانية من وقت QPU. هذا تقدير بحسن نية؛ قد يختلف استخدامك الفعلي.

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# Uncomment and modify this line as needed to install dependencies
#!pip install 'qiskit>=2.1.0' 'qiskit-ibm-runtime>=0.40.1' 'qiskit-aer>=0.17.0' 'numpy' 'pylatexenc'

مقدّمة

خوارزمية غروفر هي خوارزمية كمومية أساسية تعالج مشكلة البحث غير المنظّم: بافتراض أن لديك مجموعة من NN عنصرًا وطريقة للتحقق مما إذا كان أيّ عنصر هو الذي تبحث عنه، فكيف يمكنك العثور على العنصر المطلوب بأسرع ما يمكن؟ في الحوسبة الكلاسيكية، إذا كانت البيانات غير مرتّبة ولا يوجد هيكل لاستغلاله، فإن أفضل نهج هو التحقق من كل عنصر واحدًا تلو الآخر، مما يؤدي إلى تعقيد استعلام يبلغ O(N)O(N) — في المتوسط، ستحتاج إلى التحقق من نحو نصف العناصر قبل العثور على الهدف.

مخطط البحث غير المنظّم الكلاسيكي.

خوارزمية غروفر، التي قدّمها Lov Grover عام 1996، توضّح كيف يمكن للحاسوب الكمومي حلّ هذه المشكلة بكفاءة أعلى بكثير، إذ لا يحتاج سوى O(N)O(\sqrt{N}) خطوة للعثور على العنصر المحدَّد باحتمالية عالية. وهذا يمثّل تسريعًا تربيعيًا مقارنةً بالأساليب الكلاسيكية، وهو أمر مهم للغاية مع مجموعات البيانات الكبيرة.

تعمل الخوارزمية في السياق التالي:

  • إعداد المشكلة: لديك دالة f(x)f(x) تُرجع 1 إذا كان xx هو العنصر الذي تبحث عنه، و0 في غير ذلك. كثيرًا ما تُسمّى هذه الدالة أوراكل أو صندوق أسود، لأنك لا تستطيع معرفة البيانات إلا عبر الاستعلام عن f(x)f(x).
  • فائدة الحوسبة الكمومية: في حين تتطلّب الخوارزميات الكلاسيكية لهذه المشكلة في المتوسط N/2N/2 استعلامًا، تستطيع خوارزمية غروفر إيجاد الحلّ في ما يقارب πN/4\pi\sqrt{N}/4 استعلامًا، وهو أسرع بكثير عند قيم NN الكبيرة.
  • كيف تعمل (على مستوى عالٍ):
    • يُنشئ الحاسوب الكمومي أوّلًا تراكبًا لجميع الحالات الممكنة، ممثِّلًا جميع العناصر المحتملة في آنٍ واحد.
    • ثم يُطبّق بشكل متكرّر سلسلة من العمليات الكمومية (تكرار غروفر) التي تُضخّم احتمالية الإجابة الصحيحة وتُخفّض الإجابات الأخرى.
    • بعد عدد كافٍ من التكرارات، يُعطي قياس الحالة الكمومية الإجابة الصحيحة باحتمالية عالية.

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

مخطط عالي المستوى لخطوات تطبيق خوارزمية غروفر.

بعض الملاحظات المهمة حول خوارزمية غروفر:

  • هي الأمثل للبحث غير المنظّم: لا توجد خوارزمية كمومية قادرة على حلّ المشكلة بأقل من O(N)O(\sqrt{N}) استعلامًا.
  • توفّر تسريعًا تربيعيًا فحسب، لا أسيًا — على عكس بعض الخوارزميات الكمومية الأخرى (مثل خوارزمية Shor للتحليل إلى عوامل أولية).
  • لها تطبيقات عملية، كاحتمالية تسريع هجمات القوة الغاشمة على الأنظمة التشفيرية، وإن كان التسريع لا يكفي وحده لكسر معظم تقنيات التشفير الحديثة.

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

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

نوضّح هنا كيفية بناء أوراكلات غروفر واستخدام GroverOperator من مكتبة دوائر Qiskit لإعداد نموذج بحث غروفر بسهولة. يتيح البدائي Sampler في وقت التشغيل تنفيذ دوائر غروفر بسلاسة.

الرياضيات

لنفترض وجود دالة ff تربط السلاسل الثنائية بمتغير ثنائي واحد، أي

f:ΣnΣf: \Sigma^n \rightarrow \Sigma

مثال على ذلك المعرَّف على Σ6\Sigma^6:

f(x)={1if x={010101}0otherwise f(x)= \begin{cases} 1 \qquad \text{if }x=\{010101\}\\ 0 \qquad \text{otherwise } \end{cases}

ومثال آخر معرَّف على Σ2n\Sigma^{2n}:

f(x)={1if equal numbers of 1’s and 0’s in string0otherwise f(x)= \begin{cases} 1 \qquad \text{if equal numbers of 1's and 0's in string}\\ 0 \qquad \text{otherwise } \end{cases}

مهمّتك هي إيجاد الحالات الكمومية المقابلة لتلك الوسائط xx في f(x)f(x) التي تُطابق القيمة 1. بعبارة أخرى، أوجد جميع {x1}Σn\{x_1\}\in \Sigma^n بحيث f(x1)=1f(x_1)=1 (أو أبلّغ إن لم يكن هناك حلّ). سنُشير إلى عدم الحلول بـx0x_0. وبطبيعة الحال، سنجري ذلك على حاسوب كمومي باستخدام الحالات الكمومية، لذا من المفيد التعبير عن هذه السلاسل الثنائية كحالات:

{x1}Σn\{|x_1\rangle\} \in |\Sigma^n\rangle

باستخدام تدوين الحالة الكمومية (Dirac)، نبحث عن حالة خاصة واحدة أو أكثر {x1}\{|x_1\rangle\} ضمن مجموعة من N=2nN=2^n حالة ممكنة، حيث nn هو عدد الكيوبتات، مع الإشارة إلى عدم الحلول بـ{x0}\{|x_0\rangle\}.

يمكننا اعتبار الدالة ff مقدَّمةً من قِبل أوراكل: صندوق أسود نستطيع الاستعلام عنه لتحديد تأثيره على الحالة x|x\rangle. من الناحية العملية، كثيرًا ما نعرف الدالة، لكنها قد تكون بالغة التعقيد في التطبيق، مما يجعل تقليل عدد الاستعلامات أو تطبيقات ff أمرًا مهمًا. وبالمثل، يمكننا تصوّر نموذج يستعلم فيه شخص عن أوراكل يتحكّم فيه شخص آخر، بحيث لا نعرف دالة الأوراكل بل نعرف فقط تأثيرها على حالات معيّنة عبر الاستعلام.

هذه "مشكلة بحث غير منظّم"، إذ لا يوجد في ff أي شيء مميّز يُعيننا في البحث. المخرجات غير مرتّبة ولا يُعرف أن الحلول تتمركز، وهكذا. فكّر في دلائل الهاتف الورقية القديمة كتشبيه: هذا البحث غير المنظّم يشبه تصفّح الدليل بحثًا عن رقم معيّن، لا كالبحث في قائمة أسماء مرتّبة أبجديًا.

في حالة البحث عن حلّ واحد، يتطلّب ذلك كلاسيكيًا عددًا من الاستعلامات يتناسب خطيًا مع NN. من الواضح أنك قد تجد الحلّ في أوّل محاولة، أو قد لا تجد أي حلّ في أوّل N1N-1 تخمين، مما يستدعي الاستعلام عن المدخل رقم NN للتحقق من وجود أي حلّ. ونظرًا لأن الدوال لا تحمل أي بنية يمكن استغلالها، فستحتاج إلى N/2N/2 تخمين في المتوسط. أما خوارزمية غروفر فتتطلّب عددًا من الاستعلامات أو حسابات ff يتناسب مع N\sqrt{N}.

لمحة عامة عن الدوائر في خوارزمية غروفر

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

يمكن تقسيم خوارزمية غروفر إلى المراحل التالية:

  • إعداد تراكب ابتدائي (تطبيق بوابات Hadamard على جميع الكيوبتات)
  • "تعليم" الحالة (أو الحالات) الهدف بقلب الطور
  • مرحلة "الانتشار" (diffusion) حيث تُطبَّق بوابات Hadamard وقلب الطور على جميع الكيوبتات
  • تكرارات محتملة لمرحلتَي التعليم والانتشار لتعظيم احتمالية قياس الحالة الهدف
  • القياس

مخطط دائرة كمومية يوضّح الإعداد الأساسي لخوارزمية غروفر. يستخدم هذا المثال أربعة كيوبتات. كثيرًا ما يُشار إلى بوابة التعليم ZfZ_f وطبقات الانتشار المكوّنة من H,H, وZOR,Z_{\text{OR}}, وHH مجتمعةً باسم "مشغّل غروفر". في هذا المخطط، يظهر تكرار واحد فقط من مشغّل غروفر.

بوابات Hadamard HH معروفة وتُستخدم على نطاق واسع في الحوسبة الكمومية. تُنشئ بوابة Hadamard حالات التراكب. وتحديدًا، تُعرَّف بـ

H0=12(0+1)H1=12(01)H|0\rangle = \frac{1}{\sqrt{2}}\left(|0\rangle+|1\rangle\right)\\ H|1\rangle = \frac{1}{\sqrt{2}}\left(|0\rangle-|1\rangle\right)

يُحدَّد تأثيرها على أي حالة أخرى من خلال الخطيّة. بشكل خاص، تتيح لنا طبقة من بوابات Hadamard الانتقال من الحالة الابتدائية حيث جميع الكيوبتات في 0|0\rangle (يُرمز لها بـ0n|0\rangle^{\otimes n}) إلى حالة يكون فيها لكل كيوبت احتمالية ما لقياسه إما في 0|0\rangle أو 1|1\rangle؛ مما يُتيح لنا استكشاف فضاء جميع الحالات الممكنة بطريقة مختلفة عن الحوسبة الكلاسيكية.

خاصية أساسية مترتّبة مهمة لبوابة Hadamard هي أن تطبيقها مرة ثانية يُلغي حالات التراكب:

H12(0+1)=0H12(01)=1H\frac{1}{\sqrt{2}}\left(|0\rangle+|1\rangle\right)=|0\rangle\\ H\frac{1}{\sqrt{2}}\left(|0\rangle-|1\rangle\right)=|1\rangle

سيكون هذا مهمًا بعد قليل.

تحقق من فهمك

اقرأ السؤال التالي، فكّر في إجابتك، ثم اضغط على المثلث لإظهار الحل.

انطلاقاً من تعريف بوابة هادامارد، أثبت أن تطبيق بوابة هادامارد مرة ثانية يُلغي حالات التراكب كما ذُكر أعلاه.

الإجابة:

عندما نُطبّق X على الحالة +|+\rangle، نحصل على القيمة +1، وعلى الحالة |-\rangle نحصل على -1، لذا لو كان لدينا توزيع 50-50، فسنحصل على قيمة توقع تساوي 0.

بوابة ZORZ_\text{OR} أقل شيوعاً، وتُعرَّف وفق:

ZORx={xif x=0nxif x0nxΣn\text{Z}_\text{OR}|x\rangle = \begin{cases} |x\rangle & \text{if } x = 0^n \\ -|x\rangle & \text{if } x \neq 0^n \end{cases} \qquad \forall x \in \Sigma^n

أما بوابة ZfZ_f فتُعرَّف بـ:

Zf:x(1)f(x)xxΣnZ_f:|x\rangle \rightarrow (-1)^{f(x)}|x\rangle \qquad \forall x \in \Sigma^n

لاحظ أن تأثير هذه البوابة هو أن ZfZ_f تعكس الإشارة على الحالة المستهدفة التي يكون فيها f(x)=1f(x) = 1، وتترك الحالات الأخرى دون تغيير.

على مستوى عالٍ ومجرّد، يمكنك التفكير في خطوات الدائرة على النحو التالي:

  • طبقة هادامارد الأولى: تضع الكيوبتات في تراكب لجميع الحالات الممكنة.
  • ZfZ_f: تُعلِّم الحالة (أو الحالات) المستهدفة بإضافة إشارة "-" أمامها. هذا لا يُغيِّر احتمالات القياس فوراً، لكنه يُغيِّر سلوك الحالة المستهدفة في الخطوات اللاحقة.
  • طبقة هادامارد أخرى: إشارة "-" التي أُدخِلت في الخطوة السابقة ستُغيِّر الإشارة النسبية بين بعض الحدود. بما أن بوابات هادامارد تُحوِّل خليطاً من الحالات الحسابية (0+1)/2(|0\rangle+|1\rangle)/\sqrt{2} إلى حالة حسابية واحدة 0|0\rangle، وتُحوِّل (01)/2(|0\rangle-|1\rangle)/\sqrt{2} إلى 1|1\rangle، فإن هذا الفرق في الإشارة النسبية يبدأ الآن في التأثير على الحالات المقاسة.
  • تُطبَّق طبقة أخيرة من بوابات هادامارد، ثم تُجرى القياسات. سنرى في القسم التالي كيف يعمل هذا بتفصيل أكبر.

مثال

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

هذا هو مخطط الدائرة مع الحالات الكمومية مُعلَّمة في مواضع مختلفة. لاحظ أنه مع كيوبتين فقط، هناك أربع حالات محتملة فقط يمكن قياسها في أي ظرف: 00|00\rangle، 01|01\rangle، 10|10\rangle، و11|11\rangle.

A diagram of a quantum circuit that implements Grover's algorithm on two qubits.

لنفترض أن الأوراكل (ZfZ_f، غير المعروف لنا) يُعلِّم الحالة 01|01\rangle. سنعمل على إجراءات كل مجموعة من البوابات الكمومية بما فيها الأوراكل، ونرى ما هو توزيع الحالات الممكنة عند القياس. في البداية تماماً، لدينا:

ψ0=00|\psi_0\rangle = |00\rangle

باستخدام تعريف بوابات هادامارد، لدينا:

ψ1=12(0+1)(0+1)=12(00+01+10+11)|\psi_1\rangle = \frac{1}{2}\left(|0\rangle+|1\rangle\right)\left(|0\rangle+|1\rangle\right)=\frac{1}{2}\left(|00\rangle+|01\rangle+|10\rangle+|11\rangle\right)

الآن يُعلِّم الأوراكل الحالة المستهدفة:

ψ2=12(0001+10+11)|\psi_2\rangle = \frac{1}{2}\left(|00\rangle-|01\rangle+|10\rangle+|11\rangle\right)

لاحظ أن في هذه الحالة، جميع النتائج الأربع المحتملة لها نفس احتمالية القياس. كلها لها وزن بمقدار 1/21/2، أي أن لكل منها احتمالية 1/22=1/4|1/2|^2=1/4 للقياس. لذا بينما الحالة 01|01\rangle مُعلَّمة بالطور "-"، لم يؤدِّ ذلك بعد إلى زيادة احتمالية قياس تلك الحالة. نكمل بتطبيق الطبقة التالية من بوابات هادامارد.

ψ3=14(00+01+10+11)14(0001+1011)+14(00+011011)+14(000110+11)\begin{aligned} |\psi_3\rangle = &\frac{1}{4}\left(|00\rangle+|01\rangle+|10\rangle+|11\rangle\right)\\ -&\frac{1}{4}\left(|00\rangle-|01\rangle+|10\rangle-|11\rangle\right)\\ +&\frac{1}{4}\left(|00\rangle+|01\rangle-|10\rangle-|11\rangle\right)\\ +&\frac{1}{4}\left(|00\rangle-|01\rangle-|10\rangle+|11\rangle\right) \end{aligned}

بجمع الحدود المتشابهة، نجد:

ψ3=12(00+0110+11)|\psi_3\rangle = \frac{1}{2}\left(|00\rangle+|01\rangle-|10\rangle+|11\rangle\right)

الآن ZORZ_{\text{OR}} تعكس الإشارة على جميع الحالات ما عدا 00|00\rangle:

ψ4=12(0001+1011)|\psi_4\rangle = \frac{1}{2}\left(|00\rangle-|01\rangle+|10\rangle-|11\rangle\right)

وأخيراً، نطبّق الطبقة الأخيرة من بوابات هادامارد:

ψ5=14(00+01+10+11)14(0001+1011)+14(00+011011)14(000110+11)\begin{aligned} |\psi_5\rangle =&\frac{1}{4}\left(|00\rangle+|01\rangle+|10\rangle+|11\rangle\right)\\ -&\frac{1}{4}\left(|00\rangle-|01\rangle+|10\rangle-|11\rangle\right)\\ +&\frac{1}{4}\left(|00\rangle+|01\rangle-|10\rangle-|11\rangle\right)\\ -&\frac{1}{4}\left(|00\rangle-|01\rangle-|10\rangle+|11\rangle\right) \end{aligned}

يستحق الأمر أن تعمل على جمع هذه الحدود بنفسك لتتأكد أن النتيجة هي بالفعل:

ψ5=01|\psi_5\rangle =|01\rangle

أي أن احتمالية قياس 01|01\rangle هي 100% (في غياب الضوضاء والأخطاء)، واحتمالية قياس أي حالة أخرى هي صفر.

هذا المثال بكيوبتين كان حالة نظيفة بشكل خاص؛ لن تُنتج خوارزمية غروفر دائماً احتمالية 100% لقياس الحالة المستهدفة. بل ستُضخِّم احتمالية قياس الحالة المستهدفة. كما قد يحتاج مُشغِّل غروفر إلى تكراره أكثر من مرة.

في القسم التالي، سنُطبِّق هذه الخوارزمية عملياً باستخدام حواسيب IBM® الكمومية الحقيقية.

تحفُّظ واضح

لتطبيق خوارزمية غروفر، كان علينا بناء مُشغِّل غروفر، وهذا يعني أننا يجب أن نكون قادرين على عكس الطور على الحالات التي تُحقِّق معيار الحل لدينا. وهذا يطرح السؤال: إذا كنا نعرف مجموعة حلولنا جيداً لدرجة أنه بإمكاننا تصميم دائرة كمومية لتُعلِّم كلاً منها، فما الذي نبحث عنه أصلاً؟! الجواب ثلاثي، وسنعود إليه طوال الدرس:

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

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

(3) خوارزمية غروفر لا تُعيد فقط بيانات كلاسيكية. صحيح أنه لو أجرينا قياساً للحالة النهائية بعد tt تكرار لمُشغِّل غروفر، فمن المرجح أن نحصل على معلومات كلاسيكية تُحدِّد حالة الحل. لكن ماذا لو لم نُرِد معلومات كلاسيكية؛ ماذا لو أردنا حالة الحل مُحضَّرة على حاسوب كمومي للاستخدام في خوارزمية أخرى؟ في الواقع تُنتج خوارزمية غروفر حالة كمومية بأوزان ثقيلة للحلول. لذا يمكنك توقُّع أن تجد خوارزمية غروفر كروتين فرعي في خوارزميات كمومية أكثر تعقيداً.

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

الاستيرادات العامة والنهج

نبدأ باستيراد عدة حزم ضرورية.

# Built-in modules
import math

# Imports from Qiskit
from qiskit import QuantumCircuit
from qiskit.circuit.library import grover_operator, MCMTGate, ZGate
from qiskit.visualization import plot_distribution
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

في هذا الدرس وغيره من الدروس، سنستخدم إطار عمل للحوسبة الكمومية يُعرف بـ "أنماط Qiskit"، الذي يُقسِّم سير العمل إلى الخطوات التالية:

  • الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمومية
  • الخطوة 2: تحسين المسألة للتنفيذ الكمومي
  • الخطوة 3: التنفيذ باستخدام Qiskit Runtime Primitives
  • الخطوة 4: المعالجة اللاحقة والتحليل الكلاسيكي

سنتبع هذه الخطوات بشكل عام، وإن كنا قد لا نُعلِّمها صراحةً دائماً.

النشاط 1: إيجاد حالة مستهدفة واحدة محددة

الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمومية

نحتاج بوابة استعلام الطور لوضع طور كلي (-1) على حالات الحل، وترك الحالات غير الحلول دون تأثير. بعبارة أخرى، تتطلب خوارزمية غروفر أوراكل يحدد حالة أساسية حسابية واحدة أو أكثر "مُعلَّمة"، حيث تعني "مُعلَّمة" حالةً ذات طور يساوي -1. يتم ذلك باستخدام بوابة Z المتحكَّم بها، أو تعميمها المتعدد التحكم على NN كيوبت. لفهم كيفية عمل هذا، دعنا نتأمل مثالاً محدداً لسلسلة البت {110}. نريد دائرة تعمل على الحالة ψ=q2,q1,q0|\psi\rangle = |q_2,q_1,q_0\rangle وتُطبق طوراً إذا كانت ψ=011|\psi\rangle = |011\rangle (حيث عكسنا ترتيب السلسلة الثنائية بسبب طريقة الترميز في Qiskit، التي تضع الكيوبت الأقل أهمية (عادةً 0) على اليمين).

وبالتالي، نريد دائرة ZfZ_f تحقق

Zfψ={ψifψ=011ψifψ011Z_f|\psi\rangle = \begin{cases} -|\psi\rangle \qquad \text{if} \qquad |\psi\rangle = |011\rangle \\ |\psi\rangle \qquad \text{if} \qquad |\psi\rangle \neq |011\rangle\end{cases}

يمكننا استخدام بوابة متعددة التحكم ومتعددة الأهداف (MCMTGate) لتطبيق بوابة Z يتحكم بها جميع الكيوبتات (قلب الطور إذا كانت كل الكيوبتات في حالة 1|1\rangle). بالطبع، قد تكون بعض الكيوبتات في حالتنا المطلوبة 0|0\rangle. لذا، على تلك الكيوبتات يجب أولاً تطبيق بوابة X، ثم تنفيذ بوابة Z متعددة التحكم، ثم تطبيق بوابة X أخرى للتراجع عن تغييرنا. تبدو MCMTGate هكذا:

mcmt_ex = QuantumCircuit(3)
mcmt_ex.compose(MCMTGate(ZGate(), 3 - 1, 1), inplace=True)
mcmt_ex.draw(output="mpl", style="iqp")

Output of the previous code cell

لاحظ أن كثيراً من الكيوبتات قد تشارك في عملية التحكم (هنا ثلاث كيوبتات)، لكن لا يُحدَّد أي كيوبت منفرد باعتباره هدفاً. هذا لأن الحالة الكاملة تحصل على إشارة "-" كلية (قلب الطور)؛ إذ تؤثر البوابة على جميع الكيوبتات بالتساوي. يختلف هذا عن كثير من بوابات الكيوبتات المتعددة الأخرى، كبوابة CX التي لها كيوبت تحكم واحد وكيوبت هدف واحد.

في الكود التالي، نُعرِّف بوابة استعلام الطور (أو الأوراكل) التي تفعل ما وصفناه للتو: تُعلِّم حالة أساسية مدخلة واحدة أو أكثر محددة من خلال تمثيل سلسلة البت الخاصة بها. تُستخدم بوابة MCMT لتنفيذ بوابة Z متعددة التحكم.

def grover_oracle(marked_states):
"""Build a Grover oracle for multiple marked states

Here we assume all input marked states have the same number of bits

Parameters:
marked_states (str or list): Marked states of oracle

Returns:
QuantumCircuit: Quantum circuit representing Grover oracle
"""
if not isinstance(marked_states, list):
marked_states = [marked_states]
# Compute the number of qubits in circuit
num_qubits = len(marked_states[0])

qc = QuantumCircuit(num_qubits)
# Mark each target state in the input list
for target in marked_states:
# Flip target bitstring to match Qiskit bit-ordering
rev_target = target[::-1]
# Find the indices of all the '0' elements in bitstring
zero_inds = [
ind for ind in range(num_qubits) if rev_target.startswith("0", ind)
]
# Add a multi-controlled Z-gate with pre- and post-applied X-gates (open-controls)
# where the target bitstring has a '0' entry
qc.x(zero_inds)
qc.compose(MCMTGate(ZGate(), num_qubits - 1, 1), inplace=True)
qc.x(zero_inds)
return qc

الآن نختار حالة "مُعلَّمة" محددة لتكون هدفنا، ونطبق الدالة التي عرفناها للتو. دعنا نرى نوع الدائرة التي أنشأتها.

marked_states = ["1110"]
oracle = grover_oracle(marked_states)
oracle.draw(output="mpl", style="iqp")

Output of the previous code cell

إذا كانت الكيوبتات 1-3 في حالة 1|1\rangle، والكيوبت 0 في البداية في حالة 0|0\rangle، فإن بوابة X الأولى ستقلب الكيوبت 0 إلى 1|1\rangle وستكون جميع الكيوبتات في حالة 1.|1\rangle. وهذا يعني أن بوابة MCMT ستُطبق تغييراً كلياً في الإشارة أو قلباً للطور، كما هو مطلوب. في أي حالة أخرى، إما أن تكون الكيوبتات 1-3 في حالة 0|0\rangle، أو يُقلب الكيوبت 0 إلى حالة 0|0\rangle، ولن يُطبَّق قلب الطور. نرى أن هذه الدائرة تُعلِّم فعلاً حالتنا المطلوبة 0111,|0111\rangle, أو سلسلة البت {1110}. يتكون مشغل غروفر الكامل من بوابة استعلام الطور (الأوراكل)، وطبقات هادامارد، ومشغل ZORZ_\text{OR}. يمكننا استخدام grover_operator المدمج لبناء هذا من الأوراكل الذي عرفناه أعلاه.

grover_op = grover_operator(oracle)
grover_op.decompose(reps=0).draw(output="mpl", style="iqp")

Output of the previous code cell

كما ناقشنا أعلاه، قد نحتاج إلى تطبيق مشغل غروفر عدة مرات. يمكن الحصول على العدد الأمثل من التكرارات، t,t, لتعظيم سعة الحالة المستهدفة في غياب الضوضاء من هذه الصيغة:

(2t+1)θ=(2t+1)sin1(A1N)(2t+1)A1Nπ2tπ4NA112(2t+1)\theta = (2t+1)\sin^{-1}\left( \sqrt{\frac{|A_1|}{N}}\right) \approx (2t+1)\sqrt{\frac{|A_1|}{N}} \approx \frac{\pi}{2}\\ t\approx \frac{\pi}{4} \sqrt{\frac{N}{|A_1|}}-\frac{1}{2}

حيث A1A_1 هو عدد الحلول أو الحالات المستهدفة. على أجهزة الكمبيوتر الكمي الحديثة ذات الضوضاء، قد يختلف العدد الأمثل تجريبياً من التكرارات - لكننا هنا نحسب ونستخدم هذا العدد الأمثل النظري باستخدام A1=1A_1=1.

optimal_num_iterations = math.floor(
math.pi / (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))
)
print(optimal_num_iterations)
3

لنقم الآن ببناء دائرة تتضمن بوابات هادامارد الأولية لإنشاء تراكب لجميع الحالات الممكنة، وتطبيق مشغل غروفر العدد الأمثل من المرات.

qc = QuantumCircuit(grover_op.num_qubits)
# Create even superposition of all basis states
qc.h(range(grover_op.num_qubits))
# Apply Grover operator the optimal number of times
qc.compose(grover_op.power(optimal_num_iterations), inplace=True)
# Measure all qubits
qc.measure_all()
qc.draw(output="mpl", style="iqp")

Output of the previous code cell

لقد بنينا دائرة غروفر الخاصة بنا!

الخطوة 2: تحسين المسألة لتنفيذها على الأجهزة الكمية

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

يوجد أدناه كود لحفظ بيانات اعتمادك عند الاستخدام الأول. تأكد من حذف هذه المعلومات من الدفتر بعد حفظها في بيئتك، حتى لا تتشارك بيانات اعتمادك عن طريق الخطأ عند مشاركة الدفتر. راجع Set up your IBM Cloud account وInitialize the service in an untrusted environment للاطلاع على مزيد من التوجيهات.

# To run on hardware, select the backend with the fewest number of jobs in the queue
from qiskit_ibm_runtime import QiskitRuntimeService

# Syntax for first saving your token. Delete these lines after saving your credentials.
# QiskitRuntimeService.save_account(channel='ibm_quantum_platform', instance = '<YOUR_IBM_INSTANCE_CRN>', token='<YOUR_API_KEY>', overwrite=True, set_as_default=True)
# service = QiskitRuntimeService(channel='ibm_quantum_platform')

# Load saved credentials
service = QiskitRuntimeService()

backend = service.least_busy(operational=True, simulator=False)
backend.name
qiskit_runtime_service._resolve_cloud_instances:WARNING:2025-08-08 14:14:19,931: Default instance not set. Searching all available instances.
'ibm_brisbane'

الآن نستخدم مدير التمريرات المُعدّ مسبقًا لتحسين دائرتنا الكمية للخلفية التي اخترناها.

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)

circuit_isa = pm.run(qc)
# The transpiled circuit will be very large. Only draw it if you are really curious.
# circuit_isa.draw(output="mpl", idle_wires=False, style="iqp")

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

print("The total depth is ", circuit_isa.depth())
print(
"The depth of two-qubit gates is ",
circuit_isa.depth(lambda instruction: instruction.operation.num_qubits == 2),
)
The total depth is  439
The depth of two-qubit gates is 113

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

التنفيذ باستخدام Qiskit primitives

نريد إجراء قياسات كثيرة ومعرفة الحالة الأكثر احتمالًا. هذا التضخيم في السعة هو مسألة أخذ عينات تناسب التنفيذ باستخدام primitive الـ Sampler من Qiskit Runtime.

لاحظ أن الدالة run() في Qiskit Runtime SamplerV2 تأخذ عناصر قابلة للتكرار تُسمى primitive unified blocks أو PUBs. بالنسبة للـ Sampler، كل PUB هو عنصر قابل للتكرار بالصيغة (circuit, parameter_values). على الأقل، يمكن تمرير قائمة من الدوائر الكمية.

# To run on a real quantum computer (this was tested on a Heron r2 processor and used 4 sec. of QPU time)

from qiskit_ibm_runtime import SamplerV2 as Sampler

sampler = Sampler(mode=backend)
sampler.options.default_shots = 10_000
result = sampler.run([circuit_isa]).result()
dist = result[0].data.meas.get_counts()

للاستفادة القصوى من هذه التجربة، ننصحك بشدة بتشغيل تجاربك على الحواسيب الكمية الحقيقية المتاحة من IBM Quantum. لكن إن استنفدت وقت المعالج الكمي (QPU)، يمكنك إلغاء تعليق الأسطر أدناه لإتمام هذا النشاط باستخدام محاكٍ.

# To run on local simulator:
# from qiskit.primitives import StatevectorSampler as Sampler
# sampler = Sampler()
# result = sampler.run([qc]).result()
# dist = result[0].data.meas.get_counts()

الخطوة 4: المعالجة اللاحقة وإرجاع النتيجة بالصيغة الكلاسيكية المطلوبة

الآن يمكننا رسم نتائج أخذ العينات في مخطط توزيع.

plot_distribution(dist)

Output of the previous code cell

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

اختبر فهمك

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

بحثنا للتو عن حل واحد في مجموعة تضم 24=162^4=16 حالة ممكنة. وحددنا العدد الأمثل لتكرارات مشغّل غروفر بـ t=3t=3. هل كان هذا العدد الأمثل سيزداد أم يقل لو بحثنا عن (أ) أي من عدة حلول، أو (ب) حل واحد في فضاء يضم حالات ممكنة أكثر؟

الجواب:

تذكّر أنه طالما عدد الحلول صغير مقارنةً بالفضاء الكامل للحلول، يمكننا توسيع دالة الجيب حول زوايا صغيرة واستخدام

(2t+1)θ=(2t+1)sin1A1N(2t+1)A1Nπ/2tπ4NA112(2t+1)\theta = (2t+1) \sin^{-1}{\sqrt{\frac{|\mathcal{A}_1|}{N}}}\approx (2t+1) \sqrt{\frac{|\mathcal{A}_1|}{N}} \approx \pi/2\\ t \approx \frac{\pi}{4}\sqrt{\frac{N}{|\mathcal{A}_1|}}-\frac{1}{2}

(أ) نرى من التعبير أعلاه أن زيادة عدد الحالات الحلول تُقلل عدد التكرارات. بشرط أن تظل النسبة A1N\frac{|\mathcal{A}_1|}{N} صغيرة، يمكن وصف كيف يقل tt: t 1A1.t~\frac{1}{\sqrt{|\mathcal{A}_1|}}.

(ب) مع زيادة فضاء الحلول الممكنة (NN)، يزيد عدد التكرارات المطلوبة، لكن بمعدل t Nt~\sqrt{N} فقط.

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

الجواب:

لا. لنفترض أننا كررنا النشاط الأول بـ 20 كيوبت، وشغّلنا الدائرة الكمية عددًا من المرات num_shots = 10,000. توزيع الاحتمالات المنتظم يعني أن كل حالة لها احتمال 10,000/220=0.0095410,000/2^{20}=0.00954 لقياسها مرة واحدة على الأقل. لو كان احتمال قياس الحالة المستهدفة 10 أضعاف احتمال الحلول غير الصحيحة (مع انخفاض طفيف مقابل في احتمال كل حل غير صحيح)، فستكون هناك فرصة 10% فقط لقياس الحالة المستهدفة ولو مرة واحدة. ومن المستبعد جدًا قياس الحالة المستهدفة عدة مرات، مما يجعلها غير قابلة للتمييز عن الحالات غير الحلول التي يتم الحصول عليها عشوائيًا. والخبر الجيد هو أنه يمكننا الحصول على نتائج بدقة أعلى باستخدام قمع الأخطاء والتخفيف منها.

النشاط 2: سير عمل خوارزمية استعلام دقيقة

سنبدأ هذا النشاط تمامًا كما بدأنا الأول، إلا أنك الآن ستعمل مع متحمس آخر لـ Qiskit. ستختار أنت بت-سترينغ سرية، وسيختار شريكك بت-سترينغ (مختلفة عمومًا). ستولّد كلٌّ منكما دائرة كمية تعمل كأوراكل، وستتبادلانهما. ستستخدم بعدها خوارزمية غروفر مع أوراكل شريكك لتحديد بت-سترينغه السرية.

الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمية

باستخدام الدالة grover_oracle المعرّفة أعلاه، أنشئ دائرة أوراكل لحالة واحدة أو أكثر من الحالات المُعلَّمة. تأكد من إخبار شريكك بعدد الحالات التي علّمتها، حتى يتمكن من تطبيق مشغّل غروفر العدد الأمثل من المرات. لا تجعل بت-سترينغك طويلة جدًا. 3-5 بتات تعمل بدون مشاكل تُذكر. البت-سترينغات الأطول ستؤدي إلى دوائر عميقة تتطلب تقنيات أكثر تقدمًا مثل تخفيف الأخطاء.

# Modify the marked states to mark those you wish to target.
marked_states = ["1000"]
oracle = grover_oracle(marked_states)

الآن أنشأت دائرة كمية تقلب طور حالتك المستهدفة. يمكنك حفظ هذه الدائرة بصيغة my_circuit.qpy باستخدام الصيغة أدناه.

from qiskit import qpy

# Save to a QPY file at a location where you can easily find it.
# You might want to specify a global address.
with open("C:\\Users\\...put your own address here...\\my_circuit.qpy", "wb") as f:
qpy.dump(oracle, f)

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

from qiskit import qpy

# Load the circuit from your partner's qpy file from the folder where you saved it.
with open("C:\\Users\\...file location here...\\my_circuit.qpy", "rb") as f:
circuits = qpy.load(f)

# qpy.load always returns a list of circuits
oracle_partner = circuits[0]

# You could visualize the circuit, but this would break the model of a query algorithm.
# oracle_partner.draw("mpl")

اسأل شريكك عن عدد الحالات المستهدفة التي برمجها وأدخلها أدناه.

# Update according to your partner's number of target states.
num_marked_states = 1

يُستخدم هذا في التعبير التالي لتحديد العدد الأمثل لتكرارات غروفر.

grover_op = grover_operator(oracle_partner)
optimal_num_iterations = math.floor(
math.pi / (4 * math.asin(math.sqrt(num_marked_states / 2**grover_op.num_qubits)))
)
qc = QuantumCircuit(grover_op.num_qubits)
qc.h(range(grover_op.num_qubits))
qc.compose(grover_op.power(optimal_num_iterations), inplace=True)
qc.measure_all()

الخطوة 2: تحسين المسألة لتنفيذها على العتاد الكمومي

هذه الخطوة تسير تماماً كما في السابق.

# To run on hardware, select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
backend.name

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
circuit_partner_isa = pm.run(qc)

الخطوة 3: التنفيذ باستخدام Qiskit primitives

هذه الخطوة أيضاً مطابقة لما فعلناه في النشاط الأول.

# To run on a real quantum computer (this was tested on a Heron r2 processor and used 4 seconds of QPU time)

from qiskit_ibm_runtime import SamplerV2 as Sampler

sampler = Sampler(mode=backend)
sampler.options.default_shots = 10_000
result = sampler.run([circuit_partner_isa]).result()
dist = result[0].data.meas.get_counts()

الخطوة 4: معالجة النتائج وإرجاعها بالصيغة الكلاسيكية المطلوبة

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

plot_distribution(dist)

Output of the previous code cell

تحقق من فهمك

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

من المفترض إنك حصلت على الحالة/الحالات المستهدفة لشريكك بشكل صحيح. إذا لم تحصل على ذلك، اعمل مع شريكك لتحديد أين المشكلة. اضغط أدناه لبعض الأفكار.

تلميحات:

  • اعرض/ارسم دائرة شريكك وتأكد إنها تحملت بشكل صحيح.
  • قارن الدوائر المستخدمة وقارن النتيجة المتوقعة بما حصلت عليه.
  • تحقق من عمق الدوائر المستخدمة للتأكد إن سلسلة البت لم تكن طويلة جداً أو عدد تكرارات غروفر مرتفعاً بشكل مفرط.

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

تلميحات:

  • تذكر إن مهمة الأوراكل هي قلب الإشارة على الحالة المستهدفة.
  • تذكر إن MCMTGate يقلب الإشارة على حالة ما إذا وفقط إذا كانت جميع الكيوبتات المشاركة في التحكم في حالة 1|1\rangle.
  • إذا كانت حالتك المستهدفة ستحتوي بالفعل على 1|1\rangle على كيوبت معين، فلا تحتاج لعمل أي شيء لذلك الكيوبت. إذا كانت حالتك المستهدفة تحتوي على 0|0\rangle على كيوبت معين وتريد من MCMTGate قلب الإشارة، تحتاج إلى تطبيق بوابة X على ذلك الكيوبت في أوراكلك (ثم التراجع عن بوابة X بعد MCMTGate).

كرر التجربة بتكرار واحد أقل من مشغل غروفر. هل ما زلت تحصل على الإجابة الصحيحة؟ لماذا أو لماذا لا؟

توجيه:

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

لماذا قد يستخدم أحد عدد تكرارات غروفر أقل من "العدد الأمثل" الذي تحدد هنا؟

الإجابة:

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

النشاط 3: معيار غير سلسلة بتات محددة

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

الخطوة 1:

سنكتب أولاً دالة تميّز الحالات التي لديها وزن هامينج المطلوب. سنكتبها بشكل عام لعدد غير محدد من الكيوبتات num_qubits ووزن هامينج مستهدف target_weight.

from qiskit import QuantumCircuit

def grover_oracle_hamming_weight(num_qubits, target_weight):
"""
Build a Grover oracle that marks all states with Hamming weight == target_weight.

Parameters:
num_qubits (int): Number of qubits in the circuit.
target_weight (int): The Hamming weight to mark.

Returns:
QuantumCircuit: Quantum circuit representing Grover oracle.
"""
qc = QuantumCircuit(num_qubits)
marked_count = 0
marked_list = []
for x in range(2**num_qubits):
bitstr = format(x, f"0{num_qubits}b")
if bitstr.count("1") == target_weight:
# Count the number of target states
marked_count = marked_count + 1
marked_list.append(bitstr)
# Reverse for Qiskit bit order (qubit 0 is rightmost)
rev_target = bitstr[::-1]
zero_inds = [i for i, b in enumerate(rev_target) if b == "0"]
# Apply X gates to 'open' controls (where bit is 0)
qc.x(zero_inds)
# Multi-controlled Z (as MCX conjugated by H)
if num_qubits == 1:
qc.z(0)
else:
qc.h(num_qubits - 1)
qc.mcx(list(range(num_qubits - 1)), num_qubits - 1)
qc.h(num_qubits - 1)
# Undo X gates
qc.x(zero_inds)
return qc, marked_count, marked_list
# Completing step 1
oracle, num_marked_states, marked_states = grover_oracle_hamming_weight(3, 2)
print(marked_states)
oracle.draw(output="mpl", style="iqp")
['011', '101', '110']

Output of the previous code cell

من هنا، العملية كلها متطابقة مع ما في النشاطين السابقين. للاختصار، خطوات أنماط Qiskit ستُعرض هنا في تعليقات الكود بس.

from qiskit_ibm_runtime import SamplerV2 as Sampler

# Completing step 1
oracle, num_marked_states, marked_states = grover_oracle_hamming_weight(4, 2)
oracle.draw(output="mpl", style="iqp")

grover_op = grover_operator(oracle)
grover_op.decompose().draw(output="mpl", style="iqp")
optimal_num_iterations = math.floor(
math.pi / (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))
)

qc = QuantumCircuit(grover_op.num_qubits)
qc.h(range(grover_op.num_qubits))
qc.compose(grover_op.power(optimal_num_iterations), inplace=True)
qc.measure_all()
qc.draw(output="mpl", style="iqp")

# Step 2: Optimize for running on real quantum computers

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
print(backend.name)

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
circuit_isa = pm.run(qc)

# Step 3: Execute using Qiskit primitives
# To run on a real quantum computer (this was tested on a Heron r2 processor and used 4 seconds of QPU time)

sampler = Sampler(mode=backend)
sampler.options.default_shots = 10_000
result = sampler.run([circuit_isa]).result()
dist = result[0].data.meas.get_counts()

# To run on local simulator:
# from qiskit.primitives import StatevectorSampler as Sampler
# sampler = Sampler()
# result = sampler.run([qc]).result()
# dist = result[0].data.meas.get_counts()
qiskit_runtime_service._resolve_cloud_instances:WARNING:2025-08-08 14:14:51,686: Default instance not set. Searching all available instances.
ibm_brisbane
print("The total depth is ", circuit_isa.depth())
print(
"The depth of two-qubit gates is ",
circuit_isa.depth(lambda instruction: instruction.operation.num_qubits == 2),
)
The total depth is  502
The depth of two-qubit gates is 130
num_marked_states
6
plot_distribution(dist)

Output of the previous code cell

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

المفاهيم الأساسية:

في هذه الوحدة، تعلمنا بعض الميزات الرئيسية لخوارزمية غروفر:

  • بينما تتطلب خوارزميات البحث الكلاسيكية غير المنظمة عدداً من الاستعلامات يتناسب خطياً مع حجم الفضاء N,N, فإن خوارزمية غروفر تتطلب عدداً من الاستعلامات يتناسب مع N.\sqrt{N}.
  • تتضمن خوارزمية غروفر تكرار سلسلة من العمليات (المعروفة عادةً بـ"معامل غروفر") عدداً من المرات t,t, يُختار بحيث تكون احتمالية قياس الحالات المستهدفة مثلى.
  • يمكن تشغيل خوارزمية غروفر بعدد أقل من tt تكرارات وما زالت تضخيم الحالات المستهدفة.
  • تنتمي خوارزمية غروفر إلى نموذج الحوسبة القائم على الاستعلامات، وتكون أكثر منطقية حين يتحكم شخص في البحث ويتحكم آخر في بناء الأوراكل. وقد تكون مفيدة أيضاً كروتين فرعي في عمليات حوسبة كمية أخرى.

الأسئلة

أسئلة صح/خطأ:

  1. صح/خطأ: توفر خوارزمية غروفر تحسناً أسياً مقارنة بالخوارزميات الكلاسيكية في عدد الاستعلامات اللازمة للعثور على حالة واحدة مميزة في بحث غير منظم.

  2. صح/خطأ: تعمل خوارزمية غروفر عن طريق زيادة احتمالية قياس حالة الحل بشكل تكراري.

  3. صح/خطأ: كلما زاد عدد تكرارات معامل غروفر، ارتفعت احتمالية قياس حالة الحل.

أسئلة الاختيار من متعدد:

  1. اختر الخيار الأنسب لإكمال الجملة. أفضل استراتيجية لاستخدام خوارزمية غروفر بنجاح على أجهزة الكمبيوتر الكمية الحديثة هي تكرار معامل غروفر...
  • أ. مرة واحدة فقط.
  • ب. دائماً tt مرة، لتعظيم سعة احتمالية حالة (حالات) الحل.
  • ج. حتى tt مرة، وإن كان عدد أقل قد يكفي لإبراز حالات الحل.
  • د. لا يقل عن 10 مرات.
  1. يظهر هنا دائرة استعلام الطور التي تعمل كأوراكل لتمييز حالة معينة بقلب الطور. أي من الحالات التالية يتم تمييزها بواسطة هذه الدائرة؟

An image of a simple grover oracle.

  • أ. 0000|0000\rangle
  • ب. 0101|0101\rangle
  • ج. 0110|0110\rangle
  • د. 1001|1001\rangle
  • هـ. 1010|1010\rangle
  • و. 1111|1111\rangle
  1. افترض أنك تريد البحث عن ثلاث حالات مميزة من مجموعة مكونة من 128. ما هو العدد الأمثل من تكرارات معامل غروفر لتعظيم سعات الحالات المميزة؟
  • أ. 1
  • ب. 3
  • ج. 5
  • د. 6
  • هـ. 20
  • و. 33

أسئلة للنقاش:

  1. ما هي الشروط الأخرى التي قد تستخدمها في الأوراكل الكمي؟ فكر في شروط يسهل صياغتها أو فرضها على سلسلة بتات لكنها لا تقتصر على كتابة سلاسل البتات المميزة فقط.