דלג לתוכן הראשי

האלגוריתם של דויטש

האלגוריתם של דויטש פותר את בעיית הזוגיות עבור המקרה המיוחד שבו n=1.n = 1. בהקשר של חישוב קוונטי, בעיה זו מכונה לעיתים בעיית דויטש, ונאמץ מינוח זה לאורך השיעור.

ליתר דיוק, הקלט מיוצג על-ידי פונקציה f:ΣΣf:\Sigma \rightarrow \Sigma מביט אחד לביט אחד. ישנן ארבע פונקציות כאלה:

af1(a)0010af2(a)0011af3(a)0110af4(a)0111\rule[-10mm]{0mm}{10mm} \begin{array}{c|c} a & f_1(a)\\ \hline 0 & 0\\ 1 & 0 \end{array} \qquad \begin{array}{c|c} a & f_2(a)\\ \hline 0 & 0\\ 1 & 1 \end{array} \qquad \begin{array}{c|c} a & f_3(a)\\ \hline 0 & 1\\ 1 & 0 \end{array} \qquad \begin{array}{c|c} a & f_4(a)\\ \hline 0 & 1\\ 1 & 1 \end{array}

הראשונה והאחרונה מבין הפונקציות הללו הן קבועות, ושתי האמצעיות הן מאוזנות, כלומר שני ערכי הפלט האפשריים של הפונקציה מופיעים מספר שווה של פעמים כשעוברים על כל הקלטים. בעיית דויטש היא לקבוע לאיזו משתי הקטגוריות הללו שייכת פונקציית הקלט: קבועה או מאוזנת.

בעיית דויטש

קלט: פונקציה f:{0,1}{0,1}f:\{0,1\}\rightarrow\{0,1\}
פלט: 00 אם ff קבועה, 11 אם ff מאוזנת

אם נתבונן בפונקציה ff של בעיית דויטש כייצוג של גישה אקראית למחרוזת, אנחנו מדברים על מחרוזת בת שני ביטים: f(0)f(1).f(0)f(1).

functionstringf100f201f310f411\begin{array}{cc} \mathsf{function} & \mathsf{string}\\ \hline f_1 & 00 \\ f_2 & 01 \\ f_3 & 10 \\ f_4 & 11 \end{array}

כשמסתכלים על זה כך, בעיית דויטש היא לחשב את הזוגיות (או, בשפה שקולה, את ה-XOR) של שני הביטים.

כל אלגוריתם קלסי המפנה שאילתות ופותר בעיה זו נכון חייב לשאול את שני הביטים: f(0)f(0) ו-f(1).f(1). אם נדע למשל ש-f(1)=1,f(1) = 1, התשובה עדיין יכולה להיות 00 או 11, בהתאם לכך שה-f(0)=1f(0) = 1 או f(0)=0f(0) = 0, בהתאמה. כך הדבר בכל מקרה אחר; ידיעת ביט אחד בלבד משניים אינה מספקת שום מידע על הזוגיות שלהם. כך, המעגל הבוליאני שתואר בסעיף הקודם הוא הטוב ביותר שאפשר לעשות מבחינת מספר השאילתות הדרוש לפתרון הבעיה.

תיאור מעגל קוונטי

האלגוריתם של דויטש פותר את בעיית דויטש בשאילתה אחת בלבד, ובכך מספק יתרון מדיד של חישוב קוונטי על פני קלסי. אמנם מדובר ביתרון צנוע — שאילתה אחת לעומת שתיים — אבל צריך להתחיל מאיפשהו. לפעמים להתקדמות מדעית יש מקורות בלתי-מרשימים לכאורה.

הנה מעגל קוונטי המתאר את האלגוריתם של דויטש:

האלגוריתם של דויטש

ניתוח

כדי לנתח את האלגוריתם של דויטש, נעקוב אחר פעולת המעגל לעיל ונזהה את מצבי ה-Qubitים בנקודות המסומנות בתרשים:

מצבים במהלך האלגוריתם של דויטש

המצב ההתחלתי הוא 10,\vert 1\rangle \vert 0 \rangle, ושתי פעולות ה-Hadamard בצד שמאל של המעגל ממירות מצב זה ל

π1=+=12(01)0+12(01)1.\vert \pi_1 \rangle = \vert - \rangle \vert + \rangle = \frac{1}{2} \bigl( \vert 0\rangle - \vert 1\rangle \bigr) \vert 0\rangle + \frac{1}{2} \bigl( \vert 0\rangle - \vert 1\rangle \bigr) \vert 1\rangle.

(כמו תמיד, אנחנו עוקבים אחר מוסכמת הסדר של Qiskit, שמציבה את ה-Qubit העליון מימין ואת התחתון משמאל.) אולי זה מרגיש לא אינטואיטיבי לכתוב את מצב המכפלה הזה באופן מבוזר חלקית (כשמצבי ה-Qubit מספר 1 נפרדים החוצה), אבל זה יקצר את הביטויים שלנו בהמשך.

לאחר מכן מתבצעת פעולת השער UfU_f. לפי הגדרת השער UfU_f, ערך הפונקציה ff עבור המצב הקלסי של ה-Qubit העליון/הימני ביותר מבוצע עליו XOR על ה-Qubit התחתון/השמאלי ביותר, מה שממיר את π1\vert \pi_1\rangle למצב

π2=12(0f(0)1f(0))0+12(0f(1)1f(1))1.\vert \pi_2 \rangle = \frac{1}{2} \bigl( \vert 0 \oplus f(0) \rangle - \vert 1 \oplus f(0) \rangle \bigr) \vert 0 \rangle + \frac{1}{2} \bigl( \vert 0 \oplus f(1) \rangle - \vert 1 \oplus f(1) \rangle \bigr) \vert 1 \rangle.

אפשר לפשט את הביטוי הזה על-ידי הבחנה שהנוסחה

0a1a=(1)a(01)\vert 0 \oplus a\rangle - \vert 1 \oplus a\rangle = (-1)^a \bigl( \vert 0\rangle - \vert 1\rangle \bigr)

עובדת עבור שני הערכים האפשריים aΣ.a\in\Sigma. בצורה מפורשת יותר, שני המקרים הם כדלקמן.

0010=01=(1)0(01)0111=10=(1)1(01)\begin{aligned} \vert 0 \oplus 0\rangle - \vert 1 \oplus 0\rangle & = \vert 0 \rangle - \vert 1 \rangle = (-1)^0 \bigl( \vert 0\rangle - \vert 1\rangle \bigr)\\ \vert 0 \oplus 1\rangle - \vert 1 \oplus 1\rangle & = \vert 1 \rangle - \vert 0\rangle = (-1)^1 \bigl( \vert 0\rangle - \vert 1\rangle \bigr) \end{aligned}

כך, אפשר לבטא את π2\vert\pi_2\rangle באופן חלופי כך:

π2=12(1)f(0)(01)0+12(1)f(1)(01)1=((1)f(0)0+(1)f(1)12).\begin{aligned} \vert\pi_2\rangle & = \frac{1}{2} (-1)^{f(0)} \bigl( \vert 0 \rangle - \vert 1 \rangle \bigr) \vert 0 \rangle + \frac{1}{2} (-1)^{f(1)} \bigl( \vert 0 \rangle - \vert 1 \rangle \bigr) \vert 1 \rangle \\ & = \vert - \rangle \biggl( \frac{(-1)^{f(0)} \vert 0\rangle + (-1)^{f(1)} \vert 1\rangle}{\sqrt{2}}\biggr). \end{aligned}

קרה כאן משהו מעניין! למרות שפעולת השער UfU_f על מצבי בסיס סטנדרטיים משאירה את ה-Qubit העליון/הימני כפי שהוא ומבצעת XOR של ערך הפונקציה על ה-Qubit התחתון/השמאלי, כאן אנו רואים שמצב ה-Qubit העליון/הימני השתנה (בדרך כלל) בעוד שמצב ה-Qubit התחתון/השמאלי נותר זהה — הוא נמצא במצב \vert - \rangle לפני ואחרי פעולת השער UfU_f. תופעה זו ידועה בשם בעיטת הפאזה, ועוד נרחיב עליה בקרוב.

עם פישוט סופי אחד — הוצאת הגורם (1)f(0)(-1)^{f(0)} מחוץ לסכום — מקבלים את הביטוי הבא למצב π2\vert\pi_2\rangle:

π2=(1)f(0)(0+(1)f(0)f(1)12)={(1)f(0)+if f(0)f(1)=0(1)f(0)if f(0)f(1)=1.\begin{aligned} \vert\pi_2\rangle & = (-1)^{f(0)} \vert - \rangle \biggl( \frac{\vert 0\rangle + (-1)^{f(0) \oplus f(1)} \vert 1\rangle}{\sqrt{2}}\biggr) \\ & = \begin{cases} (-1)^{f(0)} \vert - \rangle \vert + \rangle & \text{if $f(0) \oplus f(1) = 0$}\\[1mm] (-1)^{f(0)} \vert - \rangle \vert - \rangle & \text{if $f(0) \oplus f(1) = 1$}. \end{cases} \end{aligned}

שימו לב שבביטוי זה יש לנו f(0)f(1)f(0) \oplus f(1) בחזקה של 1-1 ולא f(1)f(0),f(1) - f(0), שהיינו עשויים לצפות לו מנקודת מבט אלגברית טהורה, אבל מקבלים את אותה תוצאה בכל דרך. הסיבה היא שהערך (1)k(-1)^k לכל מספר שלם kk תלוי רק בשאלה האם kk זוגי או אי-זוגי.

הפעלת שער Hadamard הסופי על ה-Qubit העליון מותירה אותנו עם המצב

π3={(1)f(0)0if f(0)f(1)=0(1)f(0)1if f(0)f(1)=1,\vert \pi_3 \rangle = \begin{cases} (-1)^{f(0)} \vert - \rangle \vert 0 \rangle & \text{if $f(0) \oplus f(1) = 0$}\\[1mm] (-1)^{f(0)} \vert - \rangle \vert 1 \rangle & \text{if $f(0) \oplus f(1) = 1$}, \end{cases}

מה שמוביל לתוצאה הנכונה בהסתברות 11 כשה-Qubit הימני/העליון נמדד.

הערות נוספות על בעיטת הפאזה

לפני שממשיכים, בואו נסתכל על הניתוח לעיל מזווית שונה מעט שעשויה לשפוך אור על תופעת בעיטת הפאזה.

ראשית, שימו לב שהנוסחה הבאה עובדת לכל ביטים b,cΣ.b,c\in\Sigma.

bc=Xcb\vert b \oplus c\rangle = X^c \vert b \rangle

ניתן לאמת זאת בבדיקה עבור שני הערכים האפשריים c=0c = 0 ו-c=1c = 1:

b0=b=Ib=X0bb1=¬b=Xb=X1b.\begin{aligned} \vert b \oplus 0 \rangle & = \vert b\rangle = \mathbb{I} \vert b \rangle = X^0 \vert b \rangle\\ \vert b \oplus 1 \rangle & = \vert \neg b\rangle = X \vert b \rangle = X^1 \vert b \rangle. \end{aligned}

בעזרת נוסחה זו, אנחנו רואים ש

Uf(ba)=bf(a)a=(Xf(a)b)aU_f \bigl(\vert b\rangle \vert a \rangle\bigr) = \vert b \oplus f(a) \rangle \vert a \rangle = \bigl(X^{f(a)}\vert b \rangle\bigr) \vert a \rangle

לכל בחירה של ביטים a,bΣ.a,b\in\Sigma. מכיוון שנוסחה זו נכונה עבור b=0b=0 ו-b=1b=1, אנחנו רואים בעזרת לינאריות ש

Uf(ψa)=(Xf(a)ψ)aU_f \bigl( \vert \psi \rangle \vert a \rangle \bigr) = \bigl(X^{f(a)}\vert \psi \rangle\bigr) \vert a \rangle

לכל וקטורי מצב Qubit ψ,\vert \psi\rangle, ולכן

Uf(a)=(Xf(a))a=(1)f(a)a.U_f \bigl( \vert - \rangle \vert a \rangle \bigr) = \bigl(X^{f(a)} \vert - \rangle \bigr) \vert a \rangle = (-1)^{f(a)} \vert - \rangle \vert a \rangle.

המפתח שהופך את זה לעבוד הוא ש-X=.X\vert - \rangle = - \vert - \rangle. במונחים מתמטיים, הוקטור \vert - \rangle הוא וקטור עצמי של המטריצה XX בעל ערך עצמי 1.-1.

נדון בוקטורים עצמיים ובערכים עצמיים ביתר פירוט בשיעור הקרוב על אמדן פאזה ופירוק גורמים, שם תופעת בעיטת הפאזה מוכללת לפעולות יוניטריות אחרות.

תוך כדי זכירה שסקלרים עפים חופשי דרך מכפלות טנסוריות, מוצאים דרך חלופית להסביר כיצד הפעולה UfU_f ממירה את π1\vert \pi_1\rangle ל-π2\vert \pi_2\rangle בניתוח לעיל:

π2=Uf(+)=12Uf(0)+12Uf(1)=((1)f(0)0+(1)f(1)12).\begin{aligned} \vert \pi_2 \rangle & = U_f \bigl( \vert - \rangle \vert + \rangle \bigr)\\ & = \frac{1}{\sqrt{2}} U_f \bigl(\vert - \rangle \vert 0\rangle \bigr) + \frac{1}{\sqrt{2}} U_f \bigl(\vert - \rangle \vert 1\rangle \bigr)\\ & = \vert - \rangle \biggl( \frac{(-1)^{f(0)} \vert 0\rangle + (-1)^{f(1)} \vert 1\rangle}{\sqrt{2}}\biggr). \end{aligned}

מימוש ב-Qiskit

עכשיו בואו נראה איך אפשר לממש את האלגוריתם של דויטש ב-Qiskit. נתחיל בבדיקת גרסה ואז נבצע את הייבואים הנדרשים למימוש זה בלבד. עבור מימושים של אלגוריתמים אחרים שיבואו, נבצע את הייבואים הנדרשים בנפרד למען מודולריות גדולה יותר.

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

print(__version__)
2.1.1
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator

תחילה נגדיר מעגל קוונטי שמממש שער שאילתה לאחת מארבע הפונקציות f1,f_1, f2,f_2, f3,f_3, או f4f_4 מביט אחד לביט אחד שתוארו קודם. כפי שכבר ציינו, מימוש שערי שאילתות אינו חלק מהאלגוריתם של דויטש עצמו; כאן אנחנו בעצם רק מראים דרך אחת להכין את הקלט, בצורת מימוש מעגלי של שער שאילתה.

def deutsch_function(case: int):
# This function generates a quantum circuit for one of the 4 functions
# from one bit to one bit

if case not in [1, 2, 3, 4]:
raise ValueError("`case` must be 1, 2, 3, or 4.")

f = QuantumCircuit(2)
if case in [2, 3]:
f.cx(0, 1)
if case in [3, 4]:
f.x(1)
return f

אפשר לראות איך כל מעגל נראה בעזרת מתודת draw. הנה המעגל עבור הפונקציה f3.f_3.

display(deutsch_function(3).draw(output="mpl"))

פלט תא הקוד הקודם

לאחר מכן ניצור את המעגל הקוונטי בפועל של האלגוריתם של דויטש, תוך החלפת שער השאילתה במימוש מעגלי קוונטי שמועבר כארגומנט. בקרוב נחבר אחד מארבעת המעגלים שמוגדרים על-ידי הפונקציה deutsch_function שהגדרנו קודם. מכסות (barriers) כלולות כדי להציג הפרדה חזותית בין מימוש שער השאילתה לשאר המעגל.

def compile_circuit(function: QuantumCircuit):
# Compiles a circuit for use in Deutsch's algorithm.

n = function.num_qubits - 1
qc = QuantumCircuit(n + 1, n)

qc.x(n)
qc.h(range(n + 1))

qc.barrier()
qc.compose(function, inplace=True)
qc.barrier()

qc.h(range(n))
qc.measure(range(n), range(n))

return qc

שוב, אפשר לראות איך המעגל נראה בעזרת מתודת draw.

display(compile_circuit(deutsch_function(3)).draw(output="mpl"))

פלט תא הקוד הקודם

לבסוף, ניצור פונקציה שמריצה את המעגל שהוגדר קודם פעם אחת ומוציאה את התוצאה המתאימה: "קבועה" או "מאוזנת".

def deutsch_algorithm(function: QuantumCircuit):
# Determine if a one-bit function is constant or balanced.

qc = compile_circuit(function)

result = AerSimulator().run(qc, shots=1, memory=True).result()
measurements = result.get_memory()
if measurements[0] == "0":
return "constant"
return "balanced"

עכשיו אפשר להריץ את האלגוריתם של דויטש על כל אחת מארבע הפונקציות שהוגדרו לעיל.

f = deutsch_function(3)
display(deutsch_algorithm(f))
'balanced'