Udostępnij za pośrednictwem


Samouczek: implementowanie transformacji Quantum Fourier w programie Q#

W tym samouczku pokazano, jak napisać i zasymulować podstawowy program kwantowy, który działa na poszczególnych kubitach.

Chociaż Q# został utworzony głównie jako język programowania wysokiego poziomu dla programów kwantowych na dużą skalę, można go również użyć do eksplorowania niższego poziomu programowania kwantowego, czyli bezpośrednio odnoszących się do określonych kubitów. W szczególności w tym samouczku przyjrzymy się bliżej transformacji Quantum Fourier (QFT) — podprocji, która jest integralną częścią wielu większych algorytmów kwantowych.

Z tego samouczka dowiesz się, jak wykonywać następujące działania:

  • Zdefiniuj operacje kwantowe w programie Q#.
  • Zapisywanie obwodu przekształcenia Quantum Fourier
  • Symulowanie operacji kwantowej z alokacji kubitów do danych wyjściowych pomiaru.
  • Zobacz, jak symulowana funkcja falowa systemu kwantowego ewoluuje w całej operacji.

Uwaga

Ten niższy widok przetwarzania informacji kwantowych jest często opisywany pod względem obwodów kwantowych, które reprezentują sekwencyjne zastosowanie bram lub operacji do określonych kubitów systemu. W związku z tym operacje jedno-i wielokubitowe stosowane sekwencyjnie mogą być łatwo reprezentowane na diagramach obwodów. Na przykład pełna transformacja kwantowa Fourier z trzema kubitami używana w tym samouczku ma następującą reprezentację jako obwód: Diagram obwodu przekształcenia Quantum Fourier.

Napiwek

Jeśli chcesz przyspieszyć podróż obliczeń kwantowych, zapoznaj się z kodem w usłudze Azure Quantum, unikatową funkcją witryny internetowej Azure Quantum. W tym miejscu możesz uruchamiać wbudowane Q# przykłady lub własne Q# programy, generować nowy Q# kod z monitów, otwierać i uruchamiać kod w programie VS Code dla sieci Web jednym kliknięciem i zadawać Copilot wszelkie pytania dotyczące obliczeń kwantowych.

Wymagania wstępne

Tworzenie nowego Q# pliku

  1. W programie VS Code wybierz pozycję Plik > nowy plik tekstowy
  2. Zapisz plik jako plik QFTcircuit.qs. Ten plik zawiera kod Q# dla programu.
  3. Otwórz plik QFTcircuit.qs.

Zapisywanie obwodu QFT w Q#

Pierwsza część tego samouczka składa się z definiowania operacji Q#, która wykonuje transformację Main kwantową Fourier na trzech kubitach. Funkcja DumpMachine służy do obserwowania, w jaki sposób symulowana funkcja falowa systemu trzy kubitów ewoluuje w całej operacji. W drugiej części samouczka dodasz funkcje pomiaru i porównasz stany przed i po pomiarze kubitów.

Tworzysz operację krok po kroku. Skopiuj i wklej kod w poniższych sekcjach do pliku QFTcircuit.qs .

Pełny kodQ# jako odwołanie.

Importowanie wymaganych Q# bibliotek

W pliku Q# zaimportuj odpowiednie Microsoft.Quantum.* przestrzenie nazw.

import Microsoft.Quantum.Diagnostics.*;
import Microsoft.Quantum.Math.*;
import Microsoft.Quantum.Arrays.*;

// operations go here

Definiowanie operacji przy użyciu argumentów i zwracanych

Następnie zdefiniuj operację Main :

operation Main() : Unit {
    // do stuff
}

Operacja Main() nigdy nie przyjmuje argumentów, a na razie zwraca Unit obiekt, który jest analogiczny do zwracania void w języku C# lub pustej krotki, Tuple[()]w języku Python. Później zmodyfikujesz operację, aby zwrócić tablicę wyników pomiaru.

Przydzielanie kubitów

W ramach Q# operacji przydziel rejestr trzech kubitów za pomocą słowa kluczowego use . W przypadku usepolecenia kubity są automatycznie przydzielane w stanie $\ket{0}$.

use qs = Qubit[3]; // allocate three qubits

Message("Initial state |000>:");
DumpMachine();

Podobnie jak w rzeczywistych obliczeniach kwantowych, Q# nie zezwala na bezpośredni dostęp do stanów kubitów. Jednak operacja DumpMachine wyświetla bieżący stan target maszyny, dzięki czemu może zapewnić cenny wgląd w debugowanie i uczenie się, gdy jest używany razem z symulatorem pełnego stanu.

Stosowanie operacji jednokrotnych i kontrolowanych

Następnie zastosujesz operacje składające się na samą operację Main . Q# zawiera już wiele z nich i inne podstawowe operacje kwantowe w Microsoft.Quantum.Intrinsic przestrzeni nazw.

Uwaga

Pamiętaj, że Microsoft.Quantum.Intrinsic nie został zaimportowany we wcześniejszym fragmencie kodu z innymi przestrzeniami nazw, ponieważ jest on ładowany automatycznie przez kompilator dla wszystkich Q# programów.

Pierwsza zastosowana operacja to H operacja (Hadamard) do pierwszego kubitu:

Diagram przedstawiający obwód dla trzech kubitów QFT za pośrednictwem pierwszej usługi Hadamard.

Aby zastosować operację do określonego kubitu z rejestru (na przykład pojedynczego Qubit z tablicy Qubit[]), użyj notacji indeksu standardowego. Dlatego zastosowanie H operacji do pierwszego kubitu rejestru qs ma postać:

H(qs[0]);

Oprócz zastosowania H operacji do poszczególnych kubitów obwód QFT składa się przede wszystkim z kontrolowanych R1 obrotów. Operacja R1(θ, <qubit>) w ogóle pozostawia składnik $\ket{0}$ kubitu bez zmian podczas stosowania rotacji $e^{i\theta}$ do składnika $\ket{1}$.

Q# ułatwia warunek uruchomienia operacji na jednym lub wielu kubitach sterujących. Ogólnie rzecz biorąc, wywołanie jest poprzedzone elementem Controlled, a argumenty operacji zmieniają się w następujący sposób:

Op(<normal args>) $\to$ Controlled Op([<control qubits>], (<normal args>))

Należy pamiętać, że argument kubitu kontrolnego musi być tablicą, nawet jeśli jest to jeden kubit.

Kontrolowane operacje w QFT to R1 operacje, które działają na pierwszym kubitie (i kontrolowane przez drugi i trzeci kubit):

Diagram przedstawiający obwód dla trzech kubitów Quantum Fourier Transform through first kubit (Kubit) za pomocą pierwszego kubitu.

Q# W pliku wywołaj te operacje za pomocą następujących instrukcji:

Controlled R1([qs[1]], (PI()/2.0, qs[0]));
Controlled R1([qs[2]], (PI()/4.0, qs[0]));

Funkcja PI() służy do definiowania rotacji pod względem radianów pi.

Zastosuj operację SWAP

Po przeprowadzeniu odpowiednich operacji H oraz kontrolowanych rotacji na drugim i trzecim kubicie, obwód wygląda następująco:

//second qubit:
H(qs[1]);
Controlled R1([qs[2]], (PI()/2.0, qs[1]));

//third qubit:
H(qs[2]);

Na koniec zastosujesz operację SWAP do pierwszych i trzecich kubitów w celu ukończenia obwodu. Ta operacja jest niezbędna, ponieważ kwantowa transformacja Fouriera przekształca kubity w odwrotnej kolejności, więc zamiany umożliwiają bezproblemową integrację podrutyny z większymi algorytmami.

SWAP(qs[2], qs[0]);

Teraz zakończono pisanie operacji na poziomie kubitu kwantowego Fouriera przekształcanego w Q# operację:

Diagram przedstawiający obwód dla trzech kubitów Quantum Fourier Transform.

Cofanie przydziału kubitów

Ostatnim krokiem jest ponowne wywołanie DumpMachine() metody w celu wyświetlenia stanu po operacji i cofnięcia przydziału kubitów. Kubity były w stanie $\ket{0}$ po ich przydzieleniu i muszą zostać zresetowane do ich stanu początkowego ResetAll przy użyciu operacji.

Wymaganie jawnego zresetowania wszystkich kubitów do $\ket{0}$ jest podstawową funkcją Q#, ponieważ umożliwia innym operacjom dokładne poznanie ich stanu podczas korzystania z tych samych kubitów (ograniczony zasób). Ponadto zresetowanie ich zapewnia, że nie są splątane żadnymi innymi kubitami w systemie. Jeśli resetowanie nie zostanie wykonane na końcu bloku alokacji use, może zostać zgłoszony błąd środowiska uruchomieniowego.

Dodaj następujące wiersze do Q# pliku:

Message("After:");
DumpMachine();

ResetAll(qs); // deallocate qubits

Kompletna operacja QFT

Program Q# zostanie ukończony. Plik QFTcircuit.qs powinien teraz wyglądać następująco:

import Microsoft.Quantum.Diagnostics.*;
import Microsoft.Quantum.Math.*;
import Microsoft.Quantum.Arrays.*;

operation Main() : Unit {

    use qs = Qubit[3]; // allocate three qubits

    Message("Initial state |000>:");
    DumpMachine();

    //QFT:
    //first qubit:
    H(qs[0]);
    Controlled R1([qs[1]], (PI()/2.0, qs[0]));
    Controlled R1([qs[2]], (PI()/4.0, qs[0]));

    //second qubit:
    H(qs[1]);
    Controlled R1([qs[2]], (PI()/2.0, qs[1]));

    //third qubit:
    H(qs[2]);

    SWAP(qs[2], qs[0]);

    Message("After:");
    DumpMachine();

    ResetAll(qs); // deallocate qubits

}                                                                                                                                                                               

Uruchamianie obwodu QFT

Na razie Main operacja nie zwraca żadnej wartości — operacja zwraca Unit wartość. Później zmodyfikujesz operację, aby zwrócić tablicę wyników pomiaru (Result[]).

  1. Przed uruchomieniem programu sprawdź na pasku stanu w dolnej części programu VS Code, że target profil jest ustawiony na Q#: Bez ograniczeń. Aby zmienić target profil, wybierz target profil na pasku stanu, a następnie wybierz pozycję Nieograniczone z menu rozwijanego. Jeśli profil target nie jest ustawiony na Bez ograniczeń, podczas uruchamiania programu zostanie wyświetlony błąd.
  2. Aby uruchomić program, wybierz pozycję Uruchom Q# plik z listy rozwijanej ikona odtwarzania w prawym górnym rogu lub naciśnij Ctrl+F5. Program uruchamia operację Main() w domyślnym symulatorze.
  3. Dane Message wyjściowe i DumpMachine są wyświetlane w konsoli debugowania.

Jeśli interesuje Cię wpływ innych stanów wejściowych, zachęcamy do eksperymentowania z zastosowaniem innych operacji kubitu przed przekształceniem.

Dodawanie pomiarów do obwodu QFT

Wyświetlacz z funkcji DumpMachine pokazał wyniki operacji, ale niestety, fundament mechaniki kwantowej mówi, że prawdziwy system kwantowy nie może mieć takiej funkcji DumpMachine. Zamiast tego informacje są wyodrębniane przez pomiary, które w ogóle nie tylko nie dostarczają informacji o pełnym stanie kwantowym, ale mogą również znacząco zmienić sam system.

Istnieje wiele rodzajów pomiarów kwantowych, ale w tym przykładzie skupiono się na najbardziej podstawowych: przewidywanych pomiarach na pojedynczych kubitach. Po pomiarze w danej podstawie (na przykład podstawa obliczeniowa $ { \ket, \ket{0}{1} } $), stan kubitu jest przewidywany w zależności od stanu podstawy, dlatego niszcząc wszelkie superpozycje między nimi.

Modyfikowanie operacji QFT

Aby zaimplementować pomiary w Q# programie, użyj M operacji, która zwraca Result typ.

Najpierw zmodyfikuj operację Main , aby zwrócić tablicę wyników pomiaru , Result[]zamiast Unit.

operation Main() : Result[] {

Definiowanie i inicjowanie Result[] tablicy

Przed alokowaniem kubitów zadeklaruj i powiąż tablicę z trzema elementami (jedną Result dla każdego kubitu):

mutable resultArray = [Zero, size = 3];

Prefacing mutableresultArray słowa kluczowego umożliwia modyfikowanie zmiennej w dalszej części kodu, na przykład podczas dodawania wyników pomiaru.

Wykonywanie pomiarów w for pętli i dodawanie wyników do tablicy

Po wykonaniu operacji przekształcania QFT wstaw następujący kod:

for i in IndexRange(qs) {
    resultArray w/= i <- M(qs[i]);
}

Funkcja wywoływana IndexRange w tablicy (na przykład tablica kubitów qs) zwraca zakres indeksów tablicy. W tym miejscu jest używana w pętli for, aby sekwencyjnie mierzyć każdy kubit przy użyciu instrukcji M(qs[i]). Każdy mierzony Result typ ( Zero lub One) jest następnie dodawany do odpowiedniej pozycji indeksu w poleceniu resultArray update-and-reassign.

Uwaga

Składnia tej instrukcji jest unikatowa dla Q#metody , ale odpowiada podobnej zmiennej ponownego przypisania resultArray[i] <- M(qs[i]) widocznego w innych językach, takich jak F# i R.

Słowo kluczowe set jest zawsze używane do ponownego przypisania zmiennych powiązanych przy użyciu polecenia mutable.

Wrócić resultArray

Po zmierzeniu wszystkich trzech kubitów i dodaniu wyników do resultArraymożna bezpiecznie zresetować i cofnąć przydział kubitów tak jak poprzednio. Aby zwrócić miary, wstaw:

return resultArray;

Uruchamianie obwodu QFT z pomiarami

Teraz zmień położenie DumpMachine funkcji, aby wyświetlić stan przed pomiarami i po nim. Końcowy Q# kod powinien wyglądać następująco:

import Microsoft.Quantum.Diagnostics.*;
import Microsoft.Quantum.Math.*;
import Microsoft.Quantum.Arrays.*;

operation Main() : Result[] {

    mutable resultArray = [Zero, size = 3];

    use qs = Qubit[3];

    //QFT:
    //first qubit:
    H(qs[0]);
    Controlled R1([qs[1]], (PI()/2.0, qs[0]));
    Controlled R1([qs[2]], (PI()/4.0, qs[0]));

    //second qubit:
    H(qs[1]);
    Controlled R1([qs[2]], (PI()/2.0, qs[1]));

    //third qubit:
    H(qs[2]);

    SWAP(qs[2], qs[0]);

    Message("Before measurement: ");
    DumpMachine();

    for i in IndexRange(qs) {
        resultArray w/= i <- M(qs[i]);
    }

    Message("After measurement: ");
    DumpMachine();

    ResetAll(qs);
    Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
    return resultArray;

}

Napiwek

Pamiętaj, aby zapisać plik za każdym razem, gdy wprowadzasz zmianę w kodzie przed ponownym uruchomieniem.

  1. Przed uruchomieniem programu sprawdź na pasku stanu w dolnej części programu VS Code, że target profil jest ustawiony na Q#: Bez ograniczeń. Aby zmienić target profil, wybierz target profil na pasku stanu, a następnie wybierz pozycję Nieograniczone z menu rozwijanego. Jeśli profil target nie jest ustawiony na Bez ograniczeń, podczas uruchamiania programu zostanie wyświetlony błąd.
  2. Aby uruchomić program, wybierz pozycję Uruchom Q# plik z listy rozwijanej ikona odtwarzania w prawym górnym rogu lub naciśnij Ctrl+5. Program uruchamia operację Main() w domyślnym symulatorze.
  3. Dane Message wyjściowe i DumpMachine są wyświetlane w konsoli debugowania.

Dane wyjściowe powinny wyglądać mniej więcej tak:

Before measurement: 

 Basis | Amplitude      | Probability | Phase
 -----------------------------------------------
 |000⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |001⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |010⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |011⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |100⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |101⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |110⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000
 |111⟩ |  0.3536+0.0000𝑖 |    12.5000% |   0.0000

After measurement: 

 Basis | Amplitude      | Probability | Phase
 -----------------------------------------------
 |010⟩ |  1.0000+0.0000𝑖 |   100.0000% |   0.0000

Post-QFT measurement results [qubit0, qubit1, qubit2]: 

[Zero, One, Zero]

Te dane wyjściowe ilustrują kilka różnych rzeczy:

  1. Porównując zwrócony wynik z DumpMachinewstępnego pomiaru, wyraźnie nie zilustrować superpozycji po QFT w stanach bazowych. Pomiar zwraca tylko jeden stan bazowy z prawdopodobieństwem określonym przez amplitudę tego stanu w funkcji falowej systemu.
  2. Po pomiarze DumpMachinewidać, że pomiar zmienia sam stan, projektując go z początkowej superpozycji na stany bazowe do pojedynczego stanu bazowego, który odpowiada zmierzonej wartości.

Jeśli powtarzasz tę operację wiele razy, zobaczysz, że statystyki wyników zaczynają ilustrować równie ważoną superpozycję stanu po QFT, który daje wynik losowy dla każdego strzału. Jednak poza nieefektywnym i nadal niedoskonałym, byłoby to jednak jedynie odtworzenie względnych amplitud stanów bazowych, a nie względnych faz między nimi. Ten ostatni nie jest problemem w tym przykładzie, ale zobaczysz, że fazy względne pojawiają się, jeśli podano bardziej złożone dane wejściowe do QFT niż $\ket{000}$.

Q# Używanie operacji w celu uproszczenia obwodu QFT

Jak wspomniano we wprowadzeniu, znaczna część Q#mocy spoczywa w rzeczywistości, że pozwala abstrakcją obawy o radzenie sobie z poszczególnymi kubitami. Rzeczywiście, jeśli chcesz opracować pełnowymiarowe, odpowiednie programy kwantowe, martwiąc się, czy H operacja przechodzi przed lub po określonej rotacji tylko spowolni. Usługa Azure Quantum udostępnia operację ApplyQFT , której można użyć i zastosować dla dowolnej liczby kubitów.

  1. Zastąp wszystko od pierwszej H operacji do SWAP operacji, włącznie, na:

    ApplyQFT(qs);
    
  2. Kod powinien teraz wyglądać następująco

    import Microsoft.Quantum.Diagnostics.*;
    import Microsoft.Quantum.Math.*;
    import Microsoft.Quantum.Arrays.*;
    
    operation Main() : Result[] {
    
        mutable resultArray = [Zero, size = 3];
    
        use qs = Qubit[3];
    
        //QFT:
        //first qubit:
    
        ApplyQFT(qs);
    
        Message("Before measurement: ");
        DumpMachine();
    
        for i in IndexRange(qs) {
            resultArray w/= i <- M(qs[i]);
        }
    
        Message("After measurement: ");
        DumpMachine();
    
        ResetAll(qs);
        Message("Post-QFT measurement results [qubit0, qubit1, qubit2]: ");
        return resultArray;
    
    }
    
  3. Q# Uruchom program ponownie i zwróć uwagę, że dane wyjściowe są takie same jak poprzednio.

  4. Aby zobaczyć rzeczywistą korzyść z używania Q# operacji, zmień liczbę kubitów na inną niż 3:

mutable resultArray = [Zero, size = 4];

use qs = Qubit[4];
//...

W związku z tym można zastosować odpowiednią wartość QFT dla dowolnej liczby kubitów bez konieczności martwienia się o dodawanie nowych H operacji i rotacji na każdym kubitie.

Zapoznaj się z innymi Q# samouczkami:

  • Kwantowy generator liczb losowych pokazuje, jak napisać Q# program, który generuje losowe liczby z kubitów w superpozycji.
  • Algorytm wyszukiwania Grovera pokazuje, jak napisać Q# program korzystający z algorytmu wyszukiwania Grovera.
  • Splątanie kwantowe pokazuje, jak napisać Q# program, który manipuluje kubitami i mierzy je oraz demonstruje skutki superpozycji i splątania.
  • Quantum Katas to samouczki samodzielne i ćwiczenia programistyczne mające na celu nauczanie elementów obliczeń kwantowych i Q# programowania w tym samym czasie.