Algorytm Deutscha
Algorytm Deutscha rozwiązuje problem parzystości dla szczególnego przypadku, gdy 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ę z jednego bitu do jednego bitu. Istnieją cztery takie funkcje:
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.
Jeśli potraktujemy funkcję wejściową w problemie Deutscha jako reprezentację losowego dostępu do ciągu znaków, myślimy o dwubitowym ciągu:
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: i Jeśli na przykład dowiemy się, że odpowiedź nadal może wynosić lub , zależnie od tego, czy , czy 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:
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:
Stan początkowy to a dwie operacje Hadamarda po lewej stronie układu przekształcają go do postaci
(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 . Zgodnie z definicją bramki , wartość funkcji dla klasycznego stanu górnego/prawego qubitu jest XORowana na dolny/lewy qubit, co przekształca w stan
Możemy uprościć to wyrażenie, korzystając z obserwacji, że wzór
zachodzi dla obu możliwych wartości Oba przypadki wyglądają następująco:
Możemy zatem zapisać w następujący sposób:
Właśnie stało się coś interesującego! Mimo że działanie bramki 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 jest on w stanie . Zjawisko to znane jest jako phase kickback i wrócimy do niego niebawem.
Po ostatnim uproszczeniu, polegającym na wyciągnięciu czynnika przed nawias, otrzymujemy następujące wyrażenie na stan :
Zauważ, że w tym wyrażeniu w wykładniku mamy , a nie czego moglibyśmy się spodziewać patrząc czysto algebraicznie — jednak wynik jest w obu przypadkach identyczny. Wynika to z tego, że wartość dla dowolnej liczby całkowitej zależy wyłącznie od tego, czy jest parzyste, czy nieparzyste.
Po zastosowaniu ostatniej bramki Hadamarda do górnego qubitu otrzymujemy stan
który przy pomiarze prawego/górnego qubitu daje poprawny wynik z prawdopodobieństwem .
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
Można to sprawdzić, podstawiając dwie możliwe wartości i :
Korzystając z tego wzoru, widzimy, że
dla dowolnych bitów Ponieważ ten wzór jest prawdziwy dla i widzimy na mocy liniowości, że
dla wszystkich wektorów stanu qubit a zatem
Kluczem, który sprawia, że to działa, jest fakt, że W języku matematyki wektor jest wektorem własnym macierzy o wartości własnej
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 przekształca w w powyższej analizie:
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 lub 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 obwód, używając metody draw. Oto Circuit dla funkcji
display(deutsch_function(3).draw(output="mpl"))
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"))
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'