Przejdź do głównej treści

Algorytm Deutscha

Algorytm Deutscha rozwiązuje problem parzystości dla szczególnego przypadku, gdy n=1.n = 1. W kontekście obliczeń kwantowych problem ten jest czasem nazywany problemem Deutscha i takiej nomenklatury będziemy się trzymać w tej lekcji.

Dokładnie rzecz ujmując, dane wejściowe są reprezentowane przez funkcję f:ΣΣf:\Sigma \rightarrow \Sigma z jednego bitu do jednego bitu. Istnieją cztery takie funkcje:

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}

Pierwsza i ostatnia z tych funkcji są stałe, a środkowe dwie są zrównoważone (ang. balanced), co oznacza, że dwie możliwe wartości wyjściowe funkcji występują tyle samo razy, gdy przebiegamy po wszystkich wejściach. Problem Deutscha polega na ustaleniu, do której z tych dwóch kategorii należy dana funkcja wejściowa: stała czy zrównoważona.

Problem Deutscha

Wejście: funkcja f:{0,1}{0,1}f:\{0,1\}\rightarrow\{0,1\}
Wyjście: 00 jeśli ff jest stała, 11 jeśli ff jest zrównoważona

Jeśli potraktujemy funkcję wejściową ff w problemie Deutscha jako reprezentację losowego dostępu do ciągu znaków, myślimy o dwubitowym ciągu: 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}

Patrząc na to w ten sposób, problem Deutscha sprowadza się do obliczenia parzystości (lub, co równoważne, XOR) dwóch bitów.

Każdy klasyczny algorytm z zapytaniami, który poprawnie rozwiązuje ten problem, musi zapytać oba bity: f(0)f(0) i f(1).f(1). Jeśli na przykład dowiemy się, że f(1)=1,f(1) = 1, odpowiedź nadal może wynosić 00 lub 11, zależnie od tego, czy f(0)=1f(0) = 1, czy f(0)=0.f(0) = 0. Każdy inny przypadek jest podobny; znajomość tylko jednego z dwóch bitów nie dostarcza żadnej informacji o ich parzystości. Tak więc circuit logiczny opisany w poprzedniej sekcji jest najlepszym, co możemy osiągnąć pod względem liczby zapytań potrzebnych do rozwiązania tego problemu.

Opis Circuit kwantowego

Algorytm Deutscha rozwiązuje problem Deutscha za pomocą jednego zapytania, zapewniając tym samym wymierną przewagę obliczeń kwantowych nad klasycznymi. Może to być skromna przewaga — jedno zapytanie zamiast dwóch — ale od czegoś trzeba zacząć. Postęp naukowy czasem rodzi się z pozornie skromnych początków.

Oto circuit kwantowy opisujący algorytm Deutscha:

Deutsch's algorithm

Analiza

Aby przeanalizować algorytm Deutscha, prześledzmy działanie powyższego układu i określmy stany qubitów w momentach zaznaczonych na poniższym rysunku:

Stany podczas algorytmu Deutscha

Stan początkowy to 10,\vert 1\rangle \vert 0 \rangle, a dwie operacje Hadamarda po lewej stronie układu przekształcają go do postaci

π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.

(Jak zawsze, stosujemy tu konwencję kolejności qubitów przyjętą w Qiskit, gdzie górny qubit jest po prawej, a dolny po lewej.) Zapisanie tego stanu iloczynowego częściowo w postaci rozłożonej (z wyciągniętymi stanami qubitu 1) może wydawać się nieintuicyjne, jednak dzięki temu późniejsze wyrażenia będą bardziej zwięzłe.

Następnie wykonywana jest bramka UfU_f. Zgodnie z definicją bramki UfU_f, wartość funkcji ff dla klasycznego stanu górnego/prawego qubitu jest XORowana na dolny/lewy qubit, co przekształca π1\vert \pi_1\rangle w stan

π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.

Możemy uprościć to wyrażenie, korzystając z obserwacji, że wzór

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

zachodzi dla obu możliwych wartości aΣ.a\in\Sigma. Oba przypadki wyglądają następująco:

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}

Możemy zatem zapisać π2\vert\pi_2\rangle w następujący sposób:

π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}

Właśnie stało się coś interesującego! Mimo że działanie bramki UfU_f na stany bazy standardowej pozostawia górny/prawy qubit bez zmian i XORuje wartość funkcji na dolny/lewy qubit, tutaj widzimy, że stan górnego/prawego qubitu zmienił się (ogólnie rzecz biorąc), podczas gdy stan dolnego/lewego qubitu pozostał taki sam — przed i po zastosowaniu bramki UfU_f jest on w stanie \vert - \rangle. Zjawisko to znane jest jako phase kickback i wrócimy do niego niebawem.

Po ostatnim uproszczeniu, polegającym na wyciągnięciu czynnika (1)f(0)(-1)^{f(0)} przed nawias, otrzymujemy następujące wyrażenie na stan π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}

Zauważ, że w tym wyrażeniu w wykładniku 1-1 mamy f(0)f(1)f(0) \oplus f(1), a nie f(1)f(0),f(1) - f(0), czego moglibyśmy się spodziewać patrząc czysto algebraicznie — jednak wynik jest w obu przypadkach identyczny. Wynika to z tego, że wartość (1)k(-1)^k dla dowolnej liczby całkowitej kk zależy wyłącznie od tego, czy kk jest parzyste, czy nieparzyste.

Po zastosowaniu ostatniej bramki Hadamarda do górnego qubitu otrzymujemy stan

π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}

który przy pomiarze prawego/górnego qubitu daje poprawny wynik z prawdopodobieństwem 11.

Dalsze uwagi na temat phase kickback

Zanim przejdziemy dalej, przyjrzyjmy się powyższej analizie z nieco innego kąta — może to rzucić nieco światła na zjawisko phase kickback.

Na początku zauważmy, że poniższy wzór działa dla wszystkich możliwych wartości bitów b,cΣ.b,c\in\Sigma.

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

Można to sprawdzić, podstawiając dwie możliwe wartości c=0c = 0 i 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}

Korzystając z tego wzoru, widzimy, że

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

dla dowolnych bitów a,bΣ.a,b\in\Sigma. Ponieważ ten wzór jest prawdziwy dla b=0b=0 i b=1,b=1, widzimy na mocy liniowości, że

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

dla wszystkich wektorów stanu qubit ψ,\vert \psi\rangle, a zatem

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.

Kluczem, który sprawia, że to działa, jest fakt, że X=.X\vert - \rangle = - \vert - \rangle. W języku matematyki wektor \vert - \rangle jest wektorem własnym macierzy XX o wartości własnej 1.-1.

Wektory własne i wartości własne omówimy szczegółowo w nadchodzącej lekcji poświęconej estymacji fazy i faktoryzacji, gdzie zjawisko phase kickback zostanie uogólnione na inne operacje unitarne.

Mając na uwadze, że skalary swobodnie „przepływają" przez iloczyny tensorowe, znajdujemy alternatywny sposób uzasadnienia, jak operacja UfU_f przekształca π1\vert \pi_1\rangle w π2\vert \pi_2\rangle w powyższej analizie:

π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}

Implementacja w Qiskit

Zobaczmy teraz, jak możemy zaimplementować algorytm Deutscha w Qiskit. Zaczniemy od sprawdzenia wersji, a następnie wykonamy importy wymagane wyłącznie na potrzeby tej implementacji. W przypadku implementacji pozostałych algorytmów, które pojawią się później, będziemy wykonywać wymagane importy osobno, dla większej modularności.

# 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

Najpierw zdefiniujemy Circuit kwantowy implementujący query Gate dla jednej z czterech funkcji f1,f_1, f2,f_2, f3f_3 lub f4f_4 z jednego bitu na jeden bit, opisanych wcześniej. Jak już wspomnieliśmy, implementacja query Gate nie jest tak naprawdę częścią samego algorytmu Deutscha; tutaj pokazujemy po prostu jeden ze sposobów przygotowania danych wejściowych w postaci implementacji query Gate jako Circuit.

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

Możemy zobaczyć, jak wygląda każdy Circuit, używając metody draw. Oto Circuit dla funkcji f3.f_3.

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

Output of the previous code cell

Następnie stworzymy właściwy Circuit kwantowy dla algorytmu Deutscha, zastępując query Gate implementacją jako Circuit podanym jako argument. Za chwilę podłączymy jeden z czterech Circuit zdefiniowanych przez funkcję deutsch_function, którą zdefiniowaliśmy wcześniej. Bariery zostały dodane, aby pokazać wizualne oddzielenie między implementacją query Gate a resztą Circuit.

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

Ponownie możemy zobaczyć, jak wygląda Circuit, używając metody draw.

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

Output of the previous code cell

Na koniec stworzymy funkcję, która uruchamia wcześniej zdefiniowany Circuit jeden raz i zwraca odpowiedni wynik: „constant" lub „balanced".

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"

Możemy teraz uruchomić algorytm Deutscha dla dowolnej z czterech funkcji zdefiniowanych powyżej.

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