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

تخفيف أخطاء القراءة لـ Sampler primitive باستخدام M3

تقدير الاستخدام: أقل من دقيقة واحدة على معالج Heron r2 (ملاحظة: هذا تقدير فحسب. قد يتفاوت وقت التشغيل الفعلي.)

الخلفية

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

تُنفّذ إضافة M3 لـ Qiskit أسلوباً فعّالاً لتخفيف أخطاء القراءة. يشرح هذا الدليل التعليمي كيفية استخدام إضافة M3 لـ Qiskit لتخفيف أخطاء القراءة في Sampler primitive.

ما هو خطأ القراءة؟

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

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

الخلفية النظرية

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

وبدقة أكبر، لكل زوج من سلاسل البتات (i,j)(i, j)، ثمة احتمال (شرطي) Mi,j{M}_{i,j} بأن ii ستُقرأ، علماً بأن القيمة الحقيقية هي j.j. أي أن،

Mi,j=Pr(readout value is itrue value is j) for i,j(0,...,2n1),(1) {M}_{i,j} = \Pr(\text{readout value is } i | \text{true value is } j) \text{ for } i,j \in (0,...,2^n - 1), \tag{1}

حيث nn هو عدد البتات في سجل القراءة. للتوضيح، نفترض أن ii هو عدد صحيح عشري يكون تمثيله الثنائي هو سلسلة البتات التي تُسمّي حالات الأساس الحسابي. نسمي المصفوفة M{M} ذات الأبعاد 2n×2n2^n \times 2^n بـ_مصفوفة التخصيص_. بالنسبة للقيمة الحقيقية الثابتة jj، يجب أن يساوي مجموع الاحتمالات على جميع المخرجات المشوَّشة ii الرقمَ 11. أي

i=02n1Mi,j=1 for all j \sum_{i=0}^{2^n - 1} {M}_{i,j} = 1 \text{ for all } j

المصفوفة التي لا تحتوي على مدخلات سالبة وتحقق المعادلة (1) تُسمى شبه عشوائية يسارية. وتُسمى المصفوفة شبه العشوائية اليسارية أيضاً شبه عشوائية عمودية لأن مجموع كل عمود من أعمدتها يساوي 11. نُحدّد تجريبياً قيماً تقريبية لكل عنصر Mi,j{M}_{i,j} بـ إعداد كل حالة أساس j|j \rangle مراراً ثم حساب تكرارات ظهور سلاسل البتات المُعيَّنة.

إذا كان التجربة تتعلق بتقدير توزيع احتمالي على سلاسل البتات الناتجة عبر أخذ عينات متكررة، فيمكننا استخدام M{M} لتخفيف خطأ القراءة على مستوى التوزيع. الخطوة الأولى هي تكرار دائرة كمية ثابتة مرات عديدة، وإنشاء مخطط تكراري لسلاسل البتات المُعيَّنة. المخطط التكراري المُعيَّر هو التوزيع الاحتمالي المقيس على 2n2^n سلسلة بتات محتملة، ونرمز إليه بـ p~R2n{\tilde{p}} \in \mathbb{R}^{2^n}. الاحتمال المُقدَّر p~i{{\tilde{p}}}_i لأخذ عينة من سلسلة البتات ii يساوي المجموع على جميع سلاسل البتات الحقيقية jj، مع ترجيح كل منها بـ احتمال الخلط بينها وبين ii. وتأخذ هذه الصياغة شكل مصفوفة كالتالي:

p~=Mp,,(2) {\tilde{p}} = {M} {\vec{p}}, \tag{2},

حيث p{\vec{p}} هو التوزيع الحقيقي. بعبارة أخرى، يتمثل أثر خطأ القراءة في ضرب التوزيع المثالي على سلاسل البتات p{\vec{p}} في مصفوفة التخصيص M{M} لـ إنتاج التوزيع الملحوظ p~{\tilde{p}}. لقد قِسنا p~{\tilde{p}} وM{M}، لكن ليس لدينا وصول مباشر إلى p{\vec{p}}. من حيث المبدأ، سنحصل على التوزيع الحقيقي لسلاسل البتات في دائرتنا الكمية بحل المعادلة (2) عددياً للحصول على p{\vec{p}}.

قبل المتابعة، تجدر الإشارة إلى بعض الخصائص المهمة لهذا الأسلوب الساذج.

  • في الواقع العملي، لا يُحل النظام (2) بعكس M{M}. تستخدم إجراءات الجبر الخطي في المكتبات البرمجية أساليب أكثر استقراراً ودقةً وكفاءةً.
  • عند تقدير M{M}، افترضنا حدوث أخطاء قراءة فقط. وبشكل خاص، افترضنا عدم وجود أخطاء في إعداد الحالة والقياس الكمي — أو على الأقل أنها خُففت بطريقة أخرى. بقدر ما يكون هذا افتراضاً سليماً، تمثل M{M} فعلاً أخطاء القراءة فحسب. لكن حين نستخدم M{M} لتصحيح توزيع مقيس على سلاسل البتات، لا نضع مثل هذا الافتراض. بل نتوقع في الواقع أن دائرة كمية مثيرة للاهتمام ستُدخل ضوضاء، مثل أخطاء البوابات. لا يزال التوزيع "الحقيقي" يشمل تأثيرات أي أخطاء لم تُخفَّف بطريقة أخرى.

يعاني هذا الأسلوب، رغم فائدته في بعض الأحيان، من بعض القيود.

تنمو موارد الفضاء والوقت اللازمة لتقدير M{M} نمواً أسياً في nn:

  • يخضع تقدير M{M} وp~{\tilde{p}} لخطأ إحصائي ناجم عن محدودية حجم العينة. يمكن تقليص هذه الضوضاء بقدر المطلوب على حساب مزيد من التقريبات (حتى يتجاوز النطاق الزمني لانجراف معلمات الأجهزة مما يُفضي إلى أخطاء منتظمة في M{M}). غير أنه إذا لم تُوضع افتراضات على سلاسل البتات الملحوظة عند إجراء التخفيف، فإن عدد التقريبات اللازمة لتقدير M{M} ينمو نمواً أسياً على الأقل في nn.
  • M{M} مصفوفة ذات أبعاد 2n×2n2^n \times 2^n. حين يكون n>10n>10، تتجاوز كمية الذاكرة اللازمة لتخزين M{M} الذاكرة المتاحة في حاسوب محمول عالي الأداء.

من القيود الإضافية:

  • قد يحتوي التوزيع المُستعاد p{\vec{p}} على احتمال سالب واحد أو أكثر (مع بقاء مجموعها يساوي واحداً). أحد الحلول هو تقليل Mpp~2||{M} {\vec{p}} - {\tilde{p}}||^2 مع اشتراط أن يكون كل مدخل في p{\vec{p}} غير سالب. غير أن وقت تشغيل مثل هذا الأسلوب يكون أطول بمراتب من الحل المباشر للمعادلة (2).
  • يعمل إجراء التخفيف هذا على مستوى التوزيع الاحتمالي على سلاسل البتات. وبشكل خاص، لا يمكنه تصحيح الخطأ في سلسلة بتات فردية ملحوظة.

إضافة Qiskit M3: التوسع نحو سلاسل بتات أطول

يقتصر حل المعادلة (2) باستخدام إجراءات الجبر الخطي العددية المعيارية على سلاسل بتات لا يتجاوز طولها نحو 10 بتات. بيد أن M3 قادر على التعامل مع سلاسل بتات أطول بكثير. من أبرز الخاصيتين في M3 اللتين تُتيحان ذلك:

  • يُفترض أن الارتباطات في خطأ القراءة من الدرجة الثالثة وما فوقها بين مجموعات البتات ضئيلة ويمكن إهمالها. ومن حيث المبدأ، وبتكلفة مزيد من التقريبات، يمكن تقدير الارتباطات الأعلى أيضاً.
  • بدلاً من إنشاء M{M} بشكل صريح، نستخدم مصفوفة فعّالة أصغر بكثير تسجّل الاحتمالات فقط لسلاسل البتات التي جُمعت عند إنشاء p~{\tilde{p}}.

على المستوى العام، تسير الإجراءات كالتالي.

أولاً، نُنشئ لبنات البناء التي يمكننا من خلالها إنشاء وصف مُبسَّط وفعّال لـ M{M}. ثم، نُشغّل الدائرة الكمية المعنية مراراً ونجمع سلاسل البتات التي نستخدمها لإنشاء كل من p~{\tilde{p}}، وبمساعدة لبنات البناء، مصفوفة M{M} الفعّالة.

وبدقة أكبر:

  • تُقدَّر مصفوفات التخصيص أحادية الكيوبت لكل كيوبت. للقيام بذلك، نُعدّ سجل الكيوبتات في الحالة الصفرية الكلية 0...0|0 ... 0 \rangle مراراً، ثم في الحالة الواحدية الكلية 1...1|1 ... 1 \rangle، ونسجّل الاحتمال بأن كل كيوبت يُقرأ بصورة خاطئة.

  • تُعتبر الارتباطات من الدرجة الثالثة وما فوقها ضئيلة ويُهمَل أثرها.

    عوضاً عن ذلك، نُنشئ عدداً nn من مصفوفات التخصيص أحادية الكيوبت ذات الأبعاد 2×22 \times 2، وعدداً n(n1)/2n(n-1)/2 من مصفوفات التخصيص ثنائية الكيوبت ذات الأبعاد 4×44 \times 4. تُخزَّن هذه المصفوفات أحادية وثنائية الكيوبت للاستخدام لاحقاً.

  • بعد أخذ عينات متكررة من دائرة كمية لإنشاء p~{\tilde{p}}، نُنشئ تقريباً فعّالاً لـ M{M} باستخدام فقط سلاسل البتات المُعيَّنة عند إنشاء p~{\tilde{p}}. تُبنى هذه المصفوفة الفعّالة باستخدام المصفوفات أحادية وثنائية الكيوبت المذكورة في البند السابق. البُعد الخطي لهذه المصفوفة لا يتجاوز في الغالب رتبة عدد التقريبات المستخدمة في إنشاء p~{\tilde{p}}، وهو أصغر بكثير من البُعد 2n2^n لمصفوفة التخصيص الكاملة M{M}.

للاطلاع على التفاصيل التقنية حول M3، يمكنك الرجوع إلى Scalable Mitigation of Measurement Errors on Quantum Computers.

تطبيق M3 على خوارزمية كمية

سنطبّق تخفيف أخطاء القراءة باستخدام M3 على مسألة الإزاحة الخفية. إن مسألة الإزاحة الخفية، والمسائل ذات الصلة الوثيقة بها كـمسألة المجموعة الفرعية الخفية، كانت تصوّرت في الأصل في سياق الأجهزة المتسامحة مع الأخطاء (وبدقة أكبر، قبل أن يُثبت إمكانية وجود وحدات المعالجة الكمية المتسامحة مع الأخطاء!). غير أنها تُدرَس أيضاً باستخدام المعالجات المتاحة حالياً. مثال على تسريع أسي خوارزمي لنوع من مسألة الإزاحة الخفية تم الحصول عليه على وحدات المعالجة الكمية IBM® ذات 127 كيوبت يمكن إيجاده في هذه الورقة البحثية (النسخة على arXiv).

فيما يلي، جميع العمليات الحسابية بوليانية. أي أنه بالنسبة لـ a,bZ2={0,1}a, b \in \mathbb{Z}_2 = \{0, 1\}، فإن الجمع a+ba + b هو دالة XOR المنطقية. وكذلك، الضرب a×ba \times b (أو aba b) هو دالة AND المنطقية. بالنسبة لـ x,y{0,1}nx, y \in \{0, 1\}^n، يُعرَّف x+yx + y بالتطبيق البتي لـ XOR. يُعرَّف حاصل الضرب النقطي :Z2nZ2\cdot: {\mathbb{Z}_2^n} \rightarrow \mathbb{Z}_2 بـ xy=ixiyix \cdot y = \sum_i x_i y_i.

عامل Hadamard وتحويل فورييه

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

Hn=12nx,yZ2n(1)xyyx.H^{\otimes n} = \frac{1}{\sqrt{2^n}} \sum_{x,y \in {\mathbb{Z}_2^n}} (-1)^{x \cdot y} {|{y}\rangle}{\langle{x}|}.

لنأخذ حالة s{|{s}\rangle} المقابلة لسلسلة البتات الثابتة ss. بتطبيق HnH^{\otimes n}، وباستخدام xs=δx,s{\langle {x}|{s}\rangle} = \delta_{x,s}، نرى أن تحويل فورييه لـ s{|{s}\rangle} يمكن كتابته كـ

Hns=12nyZ2n(1)syy. H^{\otimes n} {|{s}\rangle} = \frac{1}{\sqrt{2^n}} \sum_{y \in {\mathbb{Z}_2^n}} (-1)^{s \cdot y} {|{y}\rangle}.

Hadamard هو معكوس نفسه، أي أن HnHn=(HH)n=InH^{\otimes n} H^{\otimes n} = (H H)^{\otimes n} = I^{\otimes n}. وبالتالي، فإن تحويل فورييه العكسي هو نفس العامل HnH^{\otimes n}. صراحةً، لدينا،

s=HnHns=Hn12nyZ2n(1)syy. {|{s}\rangle} = H^{\otimes n} H^{\otimes n} {|{s}\rangle} = H^{\otimes n} \frac{1}{\sqrt{2^n}} \sum_{y \in {\mathbb{Z}_2^n}} (-1)^{s \cdot y} {|{y}\rangle}.

مسألة الإزاحة الخفية

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

ليكن x,yZ2mx,y \in {\mathbb{Z}_2^m} سلسلتا بتات بطول mm. نُعرّف f:Z2m×Z2m{1,1}{f}: {\mathbb{Z}_2^m} \times {\mathbb{Z}_2^m} \rightarrow \{-1,1\} بـ

f(x,y)=(1)xy. {f}(x, y) = (-1)^{x \cdot y}.

ليكن a,bZ2ma,b \in {\mathbb{Z}_2^m} سلسلتا بتات ثابتتان بطول mm. نُعرّف أيضاً g:Z2m×Z2m{1,1}g: {\mathbb{Z}_2^m} \times {\mathbb{Z}_2^m} \rightarrow \{-1,1\} بـ

g(x,y)=f(x+a,y+b)=(1)(x+a)(y+b), g(x, y) = {f}(x+a, y+b) = (-1)^{(x+a) \cdot (y+b)},

حيث aa وbb معاملان (خفيان). لدينا صندوقان أسودان، أحدهما ينفّذ ff، والآخر ينفّذ gg. نفترض أننا نعلم أنهما يحسبان الدوال المعرّفة أعلاه، غير أننا لا نعلم لا aa ولا bb. اللعبة هي تحديد سلاسل البتات الخفية (الإزاحات) aa وbb عبر الاستعلام من ff وgg. من الواضح أنه إذا لعبنا اللعبة كلاسيكياً، نحتاج إلى O(2m)O(2m) استعلامات لتحديد aa وbb. على سبيل المثال، يمكننا الاستعلام عن gg بجميع أزواج السلاسل بحيث يكون أحد عنصري الزوج أصفاراً كلها، والعنصر الآخر يحتوي على عنصر واحد فقط مضبوط على 11. في كل استعلام، نتعلم عنصراً واحداً من aa أو bb. غير أننا سنرى أنه إذا كانت الصناديق السوداء مُنفَّذة كدوائر كمية، يمكننا تحديد aa وbb باستعلام واحد لكل من ff وgg.

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

الدوائر الكمية لـ ff وgg

نحتاج إلى المكونات التالية لتنفيذ ff وgg كدوائر كمية.

بالنسبة لحالتي الكيوبت الكلاسيكية الأحادية x1,y1{|{x_1}\rangle}, {|{y_1}\rangle}، مع x1,y1Z2x_1,y_1 \in \mathbb{Z}_2، يمكن كتابة بوابة ZZ المتحكَّم بها CZ{CZ} كـ

CZx1y1x1=(1)x1y1x1x1y1.{CZ} {|{x_1}\rangle}{|{y_1}\rangle}{x_1} = (-1)^{x_1 y_1} {|{x_1}\rangle}{x_1}{|{y_1}\rangle}.

سنعمل مع mm من بوابات CZ، واحدة على (x1,y1)(x_1, y_1)، وواحدة على (x2,y2)(x_2, y_2)، وهكذا حتى (xm,ym)(x_m, y_m). نسمي هذا العامل CZx,y{CZ}_{x,y}.

Uf=CZx,yU_f = {CZ}_{x,y} هو نسخة كمية من f=f(x,y){f} = {f}(x,y):

Ufxy=CZx,yxy=(1)xyxy.%\CZ_{x,y} {|#1\rangle}{z} = U_f {|{x}\rangle}{|{y}\rangle} = {CZ}_{x,y} {|{x}\rangle}{|{y}\rangle} = (-1)^{x \cdot y} {|{x}\rangle}{|{y}\rangle}.

نحتاج أيضاً إلى تنفيذ إزاحة سلسلة بتات. نرمز للعامل على سجل xx بـ Xa1XamX^{a_1}\cdots X^{a_m} بالرمز XaX_a وكذلك على سجل yy بـ Xb=Xb1XbmX_b = X^{b_1}\cdots X^{b_m}. تطبّق هذه العوامل XX أينما كان بت واحد يساوي 11، وتطبّق الهوية II أينما كان يساوي 00. ثم لدينا

XaXbxy=x+ay+b. X_a X_b {|{x}\rangle}{|{y}\rangle} = {|{x+a}\rangle}{|{y+b}\rangle}.

الصندوق الأسود الثاني gg مُنفَّذ بالأحادي UgU_g، المعطى بـ

Ug=XaXbCZx,yXaXb.%U_g {|{x}\rangle}{|{y}\rangle} = X_aX_b \CZ_{x,y} X_aX_b {|{x}\rangle}{|{y}\rangle}. U_g = X_aX_b {CZ}_{x,y} X_aX_b.

للتحقق من ذلك، نطبّق العوامل من اليمين إلى اليسار على الحالة xy{|{x}\rangle}{|{y}\rangle}. أولاً

XaXbxy=x+ay+b. X_a X_b {|{x}\rangle}{|{y}\rangle} = {|{x+a}\rangle}{|{y+b}\rangle}.

ثم،

CZx,yx+ay+b=(1)(x+a)(y+b)x+ay+b. {CZ}_{x,y} {|{x+a}\rangle}{|{y+b}\rangle} = (-1)^{(x+a)\cdot (y+b)} {|{x+a}\rangle}{|{y+b}\rangle}.

وأخيراً،

XaXb(1)(x+a)(y+b)x+ay+b=(1)(x+a)(y+b)xy, X^a X^b (-1)^{(x+a)\cdot (y+b)} {|{x+a}\rangle}{|{y+b}\rangle} = (-1)^{(x+a)\cdot (y+b)} {|{x}\rangle}{|{y}\rangle},

وهو فعلاً النسخة الكمية لـ f(x+a,y+b)f(x+a, y+b).

خوارزمية الإزاحة الخفية

الآن ندمج القطع معاً لحل مسألة الإزاحة الخفية. نبدأ بتطبيق Hadamards على السجلات المُهيَّأة في الحالة الصفرية الكلية.

H2m=HmHm0m0m=122mx,yZ2m(1)xyxy.H^{\otimes 2m} = H^{\otimes m} \otimes H^{\otimes m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} = \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{x \cdot y} {|{x}\rangle}{|{y}\rangle}.

بعد ذلك، نستعلم الأوراكل gg للوصول إلى

UgH2m0m0m=122mx,yZ2m(1)(x+a)(y+b)xyU_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} = \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{(x+a) \cdot (y+b)} {|{x}\rangle}{|{y}\rangle} 122mx,yZ2m(1)xy+xb+yaxy.\approx \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{x \cdot y + x \cdot b + y \cdot a} {|{x}\rangle}{|{y}\rangle}.

في السطر الأخير، أهملنا عامل الطور الكوني الثابت (1)ab(-1)^{a \cdot b}، ونرمز للمساواة حتى طور بـ \approx. بعد ذلك، يُضيف تطبيق الأوراكل ff عاملاً آخر من (1)xy(-1)^{x \cdot y}، ملغياً العامل الموجود بالفعل. ثم يكون لدينا:

UfUgH2m0m0m122mx,yZ2m(1)xb+yaxy.U_f U_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} \approx \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{x \cdot b + y \cdot a} {|{x}\rangle}{|{y}\rangle}.

الخطوة الأخيرة هي تطبيق تحويل فورييه العكسي، H2m=HmHmH^{\otimes 2m} = H^{\otimes m} \otimes H^{\otimes m}، مما يُنتج

H2mUfUgH2m0m0mba.H^{\otimes 2m} U_f U_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} \approx {|{b}\rangle}{|{a}\rangle}.

اكتملت الدائرة الكمية. في غياب الضوضاء، يُعيد أخذ عينات من السجلات الكمية سلاسل البتات b,ab, a باحتمال 11.

حاصل الضرب النقطي البولياني هو مثال على ما يُسمى بـ دوال bent. لن نُعرّف دوال bent هنا ولكن نكتفي بالإشارة إلى أنها "مقاومة قصوى للهجمات التي تسعى إلى استغلال الاعتماد في المخرجات على بعض الفضاء الجزئي الخطي للمدخلات." هذا الاقتباس من المقال Quantum algorithms for highly non-linear Boolean functions، الذي يقدم خوارزميات إزاحة خفية فعّالة لعدة فئات من دوال bent. تظهر الخوارزمية في هذا الدليل التعليمي في القسم 3.1 من المقال.

في الحالة الأكثر عمومية، الدائرة الكمية للعثور على إزاحة خفية sZns \in \mathbb{Z}^n هي

HnUf~HnUgHn0n=s. H^{\otimes n} U_{\tilde{f}} H^{\otimes n} U_g H^{\otimes n} {|{0}\rangle}^{\otimes n} = {|{s}\rangle}.

في الحالة العامة، ff وgg دالتان لمتغير واحد. مثالنا على حاصل الضرب النقطي يأخذ هذا الشكل إذا أخذنا f(x,y)f(z)f(x, y) \to f(z)، مع zz مساوياً لتسلسل xx وyy، وss مساوياً لتسلسل aa وbb. تتطلب الحالة العامة بالضبط أوراكلين: أوراكل لـ gg وأوراكل لـ f~\tilde{f}، حيث الأخيرة دالة تُعرف بـ المزدوج لدالة bent ff. دالة حاصل الضرب النقطي تتمتع بخاصية المزدوج الذاتي f~=f\tilde{f}=f.

في دائرتنا لإزاحة الإزاحة الخفية على حاصل الضرب النقطي، أغفلنا الطبقة الوسطى من Hadamards التي تظهر في الدائرة للحالة العامة. وبينما هذه الطبقة ضرورية في الحالة العامة، وفّرنا قدراً من العمق بحذفها، على حساب قليل من المعالجة اللاحقة لأن المخرج هو ba{|{b}\rangle}{|{a}\rangle} بدلاً من المطلوب ab{|{a}\rangle}{|{b}\rangle}.

المتطلبات

قبل البدء في هذا الدليل التعليمي، تأكد من تثبيت ما يلي:

  • Qiskit SDK v2.1 أو أحدث، مع دعم التصور
  • Qiskit Runtime v0.41 أو أحدث (pip install qiskit-ibm-runtime)
  • M3 Qiskit addon v3.0 (pip install mthree)

الإعداد

# Added by doQumentation — installs packages not in the Binder environment
%pip install -q mthree
from collections.abc import Iterator, Sequence
from random import Random
from qiskit.circuit import (
CircuitInstruction,
QuantumCircuit,
QuantumRegister,
Qubit,
)
from qiskit.circuit.library import CZGate, HGate, XGate
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
import timeit
import matplotlib.pyplot as plt
from qiskit_ibm_runtime import SamplerV2 as Sampler
import mthree

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

أولاً، نكتب الدوال لتنفيذ مسألة الإزاحة الخفية كـ QuantumCircuit.

def apply_hadamards(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:
"""Apply a Hadamard gate to every qubit."""
for q in qubits:
yield CircuitInstruction(HGate(), [q], [])

def apply_shift(
qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
"""Apply X gates where the bits of the shift are equal to 1."""
for i, q in zip(range(shift.bit_length()), qubits):
if shift >> i & 1:
yield CircuitInstruction(XGate(), [q], [])

def oracle_f(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:
"""Apply the f oracle."""
for i in range(0, len(qubits) - 1, 2):
yield CircuitInstruction(CZGate(), [qubits[i], qubits[i + 1]])

def oracle_g(
qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
) -> Iterator[CircuitInstruction]:
"""Apply the g oracle."""
yield from apply_shift(qubits, shift)
yield from oracle_f(qubits)
yield from apply_shift(qubits, shift)

def determine_hidden_shift(
qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
"""Determine the hidden shift."""
yield from apply_hadamards(qubits)
yield from oracle_g(qubits, shift)
# We omit this layer in exchange for post processing
# yield from apply_hadamards(qubits)
yield from oracle_f(qubits)
yield from apply_hadamards(qubits)

def run_hidden_shift_circuit(n_qubits, rng):
hidden_shift = rng.getrandbits(n_qubits)

qubits = QuantumRegister(n_qubits, name="q")
circuit = QuantumCircuit.from_instructions(
determine_hidden_shift(qubits, hidden_shift), qubits=qubits
)
circuit.measure_all()
# Format the hidden shift as a string.
hidden_shift_string = format(hidden_shift, f"0{n_qubits}b")
return (circuit, hidden_shift, hidden_shift_string)

def display_circuit(circuit):
return circuit.remove_final_measurements(inplace=False).draw(
"mpl", idle_wires=False, scale=0.5, fold=-1
)

سنبدأ بمثال صغير:

n_qubits = 6
random_seed = 12345
rng = Random(random_seed)
circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(
n_qubits, rng
)

print(f"Hidden shift string {hidden_shift_string}")

display_circuit(circuit)
Hidden shift string 011010

Output of the previous code cell

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

job_tags = [
f"shift {hidden_shift_string}",
f"n_qubits {n_qubits}",
f"seed = {random_seed}",
]
job_tags
['shift 011010', 'n_qubits 6', 'seed = 12345']
# Uncomment this to run the circuits on a quantum computer on IBMCloud.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=100
)

# from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2
# backend = FakeMelbourneV2()
# backend.refresh(service)

print(f"Using backend {backend.name}")

def get_isa_circuit(circuit, backend):
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(circuit)
return isa_circuit

isa_circuit = get_isa_circuit(circuit, backend)
display_circuit(isa_circuit)
Using backend ibm_kingston

Output of the previous code cell

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

# submit job for solving the hidden shift problem using the Sampler primitive
NUM_SHOTS = 50_000

def run_sampler(backend, isa_circuit, num_shots):
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags
pubs = [(isa_circuit, None, NUM_SHOTS)]
job = sampler.run(pubs)
return job

def setup_mthree_mitigation(isa_circuit, backend):
# retrieve the final qubit mapping so mthree knows which qubits to calibrate
qubit_mapping = mthree.utils.final_measurement_mapping(isa_circuit)

# submit jobs for readout error calibration
mit = mthree.M3Mitigation(backend)
mit.cals_from_system(qubit_mapping, rep_delay=None)

return mit, qubit_mapping
job = run_sampler(backend, isa_circuit, NUM_SHOTS)
mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)

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

في المناقشة النظرية أعلاه، توصلنا إلى أنه بالنسبة للمدخل abab، نتوقع الحصول على المخرج baba. ثمة تعقيد إضافي يتمثل في أنه، لأجل الحصول على دائرة أبسط (قبل عملية الترجمة)، أدرجنا بوابات CZ المطلوبة بين الأزواج المتجاورة من الكيوبتات. يُعادل ذلك تشابك سلسلتَي البتات aa و bb على شكل a1b1a2b2a1 b1 a2 b2 \ldots. وستكون سلسلة المخرج baba متشابكة بطريقة مماثلة: b1a1b2a2b1 a1 b2 a2 \ldots. تقوم الدالة unscramble أدناه بتحويل سلسلة المخرج من الشكل b1a1b2a2b1 a1 b2 a2 \ldots إلى الشكل a1b1a2b2a1 b1 a2 b2 \ldots حتى يمكن مقارنة سلسلتَي المدخل والمخرج مباشرةً.

# retrieve bitstring counts
def get_bitstring_counts(job):
result = job.result()
pub_result = result[0]
counts = pub_result.data.meas.get_counts()
return counts, pub_result
counts, pub_result = get_bitstring_counts(job)

مسافة Hamming بين سلسلتَي بتات هي عدد المواضع التي تختلف فيها البتات.

def hamming_distance(s1, s2):
weight = 0
for c1, c2 in zip(s1, s2):
(c1, c2) = (int(c1), int(c2))
if (c1 == 1 and c2 == 1) or (c1 == 0 and c2 == 0):
weight += 1

return weight
# Replace string of form a1b1a2b2... with b1a1b2a1...
# That is, reverse order of successive pairs of bits.
def unscramble(bitstring):
ps = [bitstring[i : i + 2][::-1] for i in range(0, len(bitstring), 2)]
return "".join(ps)

def find_hidden_shift_bitstring(counts, hidden_shift_string):
# convert counts to probabilities
probs = {
unscramble(bitstring): count / NUM_SHOTS
for bitstring, count in counts.items()
}

# Retrieve the most probable bitstring.
most_probable = max(probs, key=lambda x: probs[x])

print(f"Expected hidden shift string: {hidden_shift_string}")
if most_probable == hidden_shift_string:
print("Most probable bitstring matches hidden shift 😊.")
else:
print("Most probable bitstring didn't match hidden shift ☹️.")
print("Top 10 bitstrings and their probabilities:")
display(
{
k: (v, hamming_distance(hidden_shift_string, k))
for k, v in sorted(
probs.items(), key=lambda x: x[1], reverse=True
)[:10]
}
)

return probs, most_probable
probs, most_probable = find_hidden_shift_bitstring(
counts, hidden_shift_string
)
Expected hidden shift string: 011010
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their probabilities:
{'011010': (0.9743, 6),
'001010': (0.00812, 5),
'010010': (0.0063, 5),
'011000': (0.00554, 5),
'011011': (0.00492, 5),
'011110': (0.00044, 5),
'001000': (0.00012, 4),
'010000': (8e-05, 4),
'001011': (6e-05, 4),
'000010': (6e-05, 4)}

لنسجّل احتمالية سلسلة البتات الأكثر احتمالاً قبل تطبيق تخفيف أخطاء القراءة باستخدام M3.

max_probability_before_M3 = probs[most_probable]
max_probability_before_M3
0.9743

الآن نطبّق تصحيح القراءة الذي تعلّمه M3 على عدد مرات الحدوث. تُعيد الدالة apply_corrections توزيع احتمالات شبه-حقيقية. وهو قائمة من كائنات float مجموعها يساوي 11، غير أن بعض القيم قد تكون سالبة.

def perform_mitigation(mit, counts, qubit_mapping):
# mitigate readout error
quasis = mit.apply_correction(counts, qubit_mapping)

# print results
most_probable_after_m3 = unscramble(max(quasis, key=lambda x: quasis[x]))

is_hidden_shift_identified = most_probable_after_m3 == hidden_shift_string
if is_hidden_shift_identified:
print("Most probable bitstring matches hidden shift 😊.")
else:
print("Most probable bitstring didn't match hidden shift ☹️.")
print("Top 10 bitstrings and their quasi-probabilities:")
topten = {
unscramble(k): f"{v:.2e}"
for k, v in sorted(quasis.items(), key=lambda x: x[1], reverse=True)[
:10
]
}
max_probability_after_M3 = float(topten[most_probable_after_m3])
display(topten)

return max_probability_after_M3, is_hidden_shift_identified
print(f"Expected hidden shift string: {hidden_shift_string}")
max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(
mit, counts, qubit_mapping
)
Expected hidden shift string: 011010
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their quasi-probabilities:
{'011010': '1.01e+00',
'001010': '8.75e-04',
'001000': '7.38e-05',
'010000': '4.51e-05',
'111000': '2.18e-05',
'001011': '1.74e-05',
'000010': '6.42e-06',
'011001': '-7.18e-06',
'011000': '-4.53e-04',
'010010': '-1.28e-03'}

مقارنة تحديد سلسلة الإزاحة المخفية قبل تطبيق تصحيح M3 وبعده

def compare_before_and_after_M3(
max_probability_before_M3,
max_probability_after_M3,
is_hidden_shift_identified,
):
is_probability_improved = (
max_probability_after_M3 > max_probability_before_M3
)
print(f"Most probable probability before M3: {max_probability_before_M3}")
print(f"Most probable probability after M3: {max_probability_after_M3}")
if is_hidden_shift_identified and is_probability_improved:
print("Readout error mitigation effective! 😊")
else:
print("Readout error mitigation not effective. ☹️")
compare_before_and_after_M3(
max_probability_before_M3,
max_probability_after_M3,
is_hidden_shift_identified,
)
Most probable probability before M3: 0.9743
Most probable probability after M3: 1.01
Readout error mitigation effective! 😊

رسم بياني لتوضيح كيفية تغيّر وقت المعالج المطلوب بواسطة M3 مع عدد الـ shots

# Collect samples for numbers of shots varying from 5000 to 25000.
shots_range = range(5000, NUM_SHOTS + 1, 2500)
times = []
for shots in shots_range:
print(f"Applying M3 correction to {shots} shots...")
t0 = timeit.default_timer()
_ = mit.apply_correction(
pub_result.data.meas.slice_shots(range(shots)).get_counts(),
qubit_mapping,
)
t1 = timeit.default_timer()
print(f"\tDone in {t1 - t0} seconds.")
times.append(t1 - t0)

fig, ax = plt.subplots()
ax.plot(shots_range, times, "o--")
ax.set_xlabel("Shots")
ax.set_ylabel("Time (s)")
ax.set_title("Time to apply M3 correction")
Applying M3 correction to 5000 shots...
Done in 0.003321983851492405 seconds.
Applying M3 correction to 7500 shots...
Done in 0.004425413906574249 seconds.
Applying M3 correction to 10000 shots...
Done in 0.006366567220538855 seconds.
Applying M3 correction to 12500 shots...
Done in 0.0071477219462394714 seconds.
Applying M3 correction to 15000 shots...
Done in 0.00860048783943057 seconds.
Applying M3 correction to 17500 shots...
Done in 0.010026784148067236 seconds.
Applying M3 correction to 20000 shots...
Done in 0.011459112167358398 seconds.
Applying M3 correction to 22500 shots...
Done in 0.012727141845971346 seconds.
Applying M3 correction to 25000 shots...
Done in 0.01406092382967472 seconds.
Applying M3 correction to 27500 shots...
Done in 0.01546052098274231 seconds.
Applying M3 correction to 30000 shots...
Done in 0.016769016161561012 seconds.
Applying M3 correction to 32500 shots...
Done in 0.019537431187927723 seconds.
Applying M3 correction to 35000 shots...
Done in 0.019739801064133644 seconds.
Applying M3 correction to 37500 shots...
Done in 0.021093040239065886 seconds.
Applying M3 correction to 40000 shots...
Done in 0.022840639110654593 seconds.
Applying M3 correction to 42500 shots...
Done in 0.023974396288394928 seconds.
Applying M3 correction to 45000 shots...
Done in 0.026412792038172483 seconds.
Applying M3 correction to 47500 shots...
Done in 0.026364430785179138 seconds.
Applying M3 correction to 50000 shots...
Done in 0.02820305060595274 seconds.
Text(0.5, 1.0, 'Time to apply M3 correction')

Output of the previous code cell

تفسير الرسم البياني

يُظهر الرسم البياني أعلاه أن الوقت اللازم لتطبيق تصحيح M3 يتناسب خطياً مع عدد الـ shots.

التوسع إلى أحجام أكبر


```python
rng = Random(12345)
circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(
n_qubits, rng
)

print(f"Hidden shift string {hidden_shift_string}")
Hidden shift string 00000010100110101011101110010001010000110011101001101010101001111001100110000111
isa_circuit = get_isa_circuit(circuit, backend)
job = run_sampler(backend, isa_circuit, NUM_SHOTS)
mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)
counts, pub_result = get_bitstring_counts(job)
probs, most_probable = find_hidden_shift_bitstring(
counts, hidden_shift_string
)
Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their probabilities:
{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': (0.50402,
80),
'00000010100110101011101110010001010000110011100001101010101001111001100110000111': (0.0396,
79),
'00000010100110101011101110010001010000110011101001101010101001111001100100000111': (0.0323,
79),
'00000010100110101011101110010001010000110011101001101010101001101001100110000111': (0.01936,
79),
'00000010100110101011101110010011010000110011101001101010101001111001100110000111': (0.01432,
79),
'00000010100110101011101110010001010000110011101001101010101001011001100110000111': (0.0101,
79),
'00000010100110101011101110010001010000110011101001101010101001110001100110000111': (0.00924,
79),
'00000010100110101011101110010001010000010011101001101010101001111001100110000111': (0.00908,
79),
'00000010100110101011100110010001010000110011101001101010101001111001100110000111': (0.00888,
79),
'00000010100110101011101110010001010000110011101001100010101001111001100110000111': (0.0082,
79)}

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

max_probability_before_M3 = probs[most_probable]
max_probability_before_M3
0.50402
print(f"Expected hidden shift string: {hidden_shift_string}")
max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(
mit, counts, qubit_mapping
)
Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their quasi-probabilities:
{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': '9.85e-01',
'00000010100110101011101110010001010000110011100001101010101001111001100110000111': '6.84e-03',
'00000010100110101011100110010001010000110011101001101010101001111001100110000111': '3.87e-03',
'00000010100110101011101110010011010000110011101001101010101001111001100110000111': '3.42e-03',
'00000010100110101011101110010001010000110011101001101010101001111001100100000111': '3.30e-03',
'00000010100110101011101110010001010000110011101001101010101001110001100110000111': '3.28e-03',
'00000010100010101011101110010001010000110011101001101010101001111001100110000111': '2.62e-03',
'00000010100110101011101110010001010000110011101001101010101001101001100110000111': '2.43e-03',
'00000010100110101011101110010000010000110011101001101010101001111001100110000111': '1.73e-03',
'00000010100110101011101110010001010000110011101001101010101001111001000110000111': '1.63e-03'}
compare_before_and_after_M3(
max_probability_before_M3,
max_probability_after_M3,
is_hidden_shift_identified,
)
Most probable probability before M3: 0.54348
Most probable probability after M3: 0.99
Readout error mitigation effective! 😊