Esercitazione: Implementare la trasformazione Quantum Fourier in Q#

Nota

Microsoft Quantum Development Kit (QDK classico) non sarà più supportato dopo il 30 giugno 2024. Se si è uno sviluppatore QDK esistente, è consigliabile passare al nuovo azure Quantum Development Kit (QDK moderno) per continuare a sviluppare soluzioni quantistiche. Per altre informazioni, vedere Eseguire la Q# migrazione del codice a QDK moderno.

Questa esercitazione illustra come scrivere e simulare un programma quantistico di base che opera su singoli qubit.

Anche se Q# è stato creato principalmente come linguaggio di programmazione di alto livello per programmi quantistici su larga scala, può essere usato anche per esplorare il livello inferiore della programmazione quantistica, cioè l'indirizzamento diretto di qubit specifici. In particolare, questa esercitazione esamina in modo più approfondito quantum Fourier Transform (QFT), una subroutine integrale a molti algoritmi quantistici più grandi.

In questa esercitazione si apprenderà come:

  • Definire le operazioni quantistico in Q#.
  • Scrivere il circuito Quantum Fourier Transform
  • Simulare un'operazione quantistica dall'allocazione di qubit all'output di misurazione.
  • Osservare l'evoluzione della funzione d'onda simulata del sistema quantistico durante l'operazione.

Nota

Questa visualizzazione di livello inferiore dell'elaborazione di informazioni quantistiche viene spesso descritta in termini di circuiti quantistici, che rappresentano l'applicazione sequenziale di controlli, o operazioni, a qubit specifici di un sistema. Di conseguenza, le operazioni a singolo qubit e a più qubit applicate in sequenza possono essere immediatamente rappresentate in diagrammi di circuito. Ad esempio, la trasformazione Quantum Fourier a tre qubit completa usata in questa esercitazione ha la rappresentazione seguente come circuito: Diagramma di un circuito Di trasformazione Quantum Fourier.

Suggerimento

Per accelerare il percorso di calcolo quantistico, vedere Codice con Azure Quantum, una funzionalità univoca del sito Web di Azure Quantum. In questo caso, è possibile eseguire esempi predefiniti Q# o programmi personalizzati Q# , generare nuovo Q# codice dalle richieste, aprire ed eseguire il codice in VS Code per il Web con un solo clic e porre a Copilot eventuali domande sul calcolo quantistico.

Prerequisiti

Create un nuovo Q# file

  1. In VS Code selezionare File > nuovo file di testo
  2. Salvare il file come QFTcircuit.qs. Questo file conterrà il Q# codice per il programma.
  3. Aprire QFTcircuit.qs.

Scrivere un circuito QFT in Q#

La prima parte di questa esercitazione consiste nella definizione dell'operazione Perform3qubitQFT di Q#, che esegue la trasformata di Fourier quantistica su tre qubit. La funzione DumpMachine viene usata per osservare l'evoluzione della funzione d'onda simulata del sistema a tre qubit nell'operazione. Nella seconda parte dell'esercitazione si aggiungerà la funzionalità di misurazione e si confronteranno gli stati pre-misurazione e post-misurazione dei qubit.

L'operazione verrà compilata passo per passo. Copiare e incollare il codice nelle sezioni seguenti nel file QFTcircuit.qs .

È possibile visualizzare il codice completo Q# per questa sezione come riferimento.

Spazi dei nomi per accedere ad altre operazioni Q#

All'interno del file Q# definire lo spazio dei nomi NamespaceQFT, a cui accede il compilatore. Per fare in modo che questa operazione usi le operazioni Q# esistenti, aprire gli spazi dei nomi Microsoft.Quantum.* pertinenti.

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    // operations go here
}

Definire operazioni con argomenti e risultati restituiti

Definire quindi l'operazione Perform3qubitQFT:

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

Per il momento, l'operazione non accetta argomenti e restituisce un oggetto Unit, che è analogo alla restituzione di void in C# o di una tupla vuota, Tuple[()], in Python. In seguito si modificherà l'operazione in modo che restituisca una matrice di risultati della misurazione.

Allocare qubit

All'interno dell'operazione Q# allocare un registro di tre qubit con la use parola chiave . Con use, i qubit vengono allocati automaticamente nello stato $\ket{0}$.

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

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

Come in un calcolo quantistico reale, Q# non consente di accedere direttamente agli stati del qubit. Tuttavia, l'operazione DumpMachine stampa lo target stato corrente della macchina, in modo che possa fornire informazioni utili per il debug e l'apprendimento quando viene usato in combinazione con il simulatore di stato completo.

Applicare operazioni a qubit singolo e controllato

Successivamente, si applicano le operazioni che costituiscono l'operazione Perform3qubitQFT stessa. Q# contiene già queste e molte altre operazioni quantistiche di base nello spazio dei nomi Microsoft.Quantum.Intrinsic.

La prima operazione applicata è l'operazione H (Hadamard) al primo qubit:

Diagramma che mostra un circuito per tre qubit QFT fino al primo hadamard.

Per applicare un'operazione a un qubit specifico da un registro (ad esempio, un singolo Qubit da una matrice Qubit[]), usare la notazione di indice standard. Quindi, l'applicazione dell'operazione H al primo qubit del registro qs assume il formato seguente:

H(qs[0]);

Oltre ad applicare l'operazione H ai singoli qubit, il circuito QFT è costituito principalmente da rotazioni R1 controllate. Un'operazione R1(θ, <qubit>) in generale lascia invariato il componente $\ket{0}$ del qubit applicando una rotazione di $e^{i\theta}$ al componente $\ket{1}$.

Q# semplifica il condizionamento dell'esecuzione di un'operazione su uno o più qubit di controllo. In generale, la chiamata è preceduta da Controlled e gli argomenti dell'operazione cambiano come segue:

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

Si noti che l'argomento qubit di controllo deve essere una matrice, anche se è per un singolo qubit.

Le operazioni controllate nel QFT sono le R1 operazioni che agiscono sul primo qubit (e controllate dal secondo e dal terzo qubit):

Diagramma che mostra un circuito per tre qubit Quantum Fourier Transform tramite il primo qubit.

Nel file Q# chiamare queste operazioni con queste istruzioni:

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

La funzione PI() viene usata per definire le rotazioni in termini di radianti pi greco.

Applicare l'operazione SWAP

Dopo aver applicato le operazioni pertinenti H e le rotazioni controllate al secondo e al terzo qubit, il circuito ha un aspetto simile al seguente:

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

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

Infine, si applica un'operazione SWAP al primo e al terzo qubit per completare il circuito. Ciò è necessario perché la natura della trasformata di Fourier quantistica restituisce i qubit in ordine inverso, quindi gli scambi consentono una perfetta integrazione della subroutine in algoritmi più grandi.

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

A questo punto è stata completata la scrittura delle operazioni a livello di qubit della trasformata di Fourier quantistica nell'operazione Q#:

Diagramma che mostra un circuito per tre qubit Quantum Fourier Transform.

Deallocare i qubit

L'ultimo passaggio consiste nel chiamare di nuovo DumpMachine() per visualizzare lo stato post-operazione e per deallocare i qubit. Al momento dell'allocazione i qubit si trovavano nello stato $\ket{0}$ e devono essere reimpostati allo stato iniziale usando l'operazione ResetAll.

Richiedere che tutti i qubit vengano reimpostati in modo esplicito su $\ket{0}$ è una funzionalità di base di Q#, in quanto consente ad altre operazioni di conoscere il proprio stato esattamente quando iniziano a usare gli stessi qubit (una risorsa scarsa). Inoltre, ciò garantisce che non ci siano entanglement con altri qubit nel sistema. Se la reimpostazione non viene eseguita alla fine di un blocco di allocazione use, potrebbe essere generato un errore di runtime.

Aggiungere la riga seguente al file Q#:

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

ResetAll(qs); // deallocate qubits

Operazione QFT completa

Il Q# programma viene completato. Il file QFTcircuit.qs dovrebbe ora essere simile al seguente:

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3qubitQFT() : 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

    }
}

Eseguire il circuito QFT

Per il momento, l'operazione Perform3qubitQFT non restituisce alcun valore. L'operazione restituisce Unit il valore. Successivamente, si modificherà l'operazione per restituire una matrice di risultati di misurazione (Result[]).

  1. Quando si esegue un Q# programma, è necessario aggiungere un EntryPoint al Q# file. Questo attributo indica al compilatore che questa operazione è il punto di ingresso del programma. Aggiungere la riga seguente all'inizio del file prima dell'operazione Q#Perform3qubitQFT :

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. Prima di eseguire il programma, è necessario impostare il target profilo su Senza restrizioni. Selezionare Visualizza -> Riquadro comandi, cercare QIR, selezionare Q#: Impostare il profilo di Azure Quantum QIR targete quindi selezionare Q#: senza restrizioni.

  3. Per eseguire il programma, selezionare Esegui Q# file dall'icona di riproduzione nell'elenco a discesa in alto a destra oppure premere CTRL+F5. Il programma esegue l'operazione o la funzione contrassegnata con l'attributo @EntryPoint() nel simulatore predefinito.

  4. Gli Message output e DumpMachine vengono visualizzati nella console di debug.

Nota

Se il target profilo non è impostato su Senza restrizioni, verrà visualizzato un errore quando si esegue il programma.

Informazioni sull'output del circuito QFT

Quando viene chiamato nel simulatore di stato completo, DumpMachine() fornisce queste rappresentazioni multiple della funzione d'onda dello stato quantistico. I possibili stati di un sistema $n$-qubit possono essere rappresentati da stati di base computazionali $2^n$, ognuno con un coefficiente complesso corrispondente (ampiezza e fase). Gli stati di base computazionali corrispondono a tutte le possibili stringhe binarie di lunghezza $n$, cioè tutte le possibili combinazioni di stati di qubit $\ket{0}$ e $\ket{1}$, in cui ogni cifra binaria corrisponde a un singolo qubit.

La prima riga fornisce un commento con gli ID dei qubit corrispondenti nell'ordine significativo. Se il qubit 2 è "il più significativo", significa che nella rappresentazione binaria del vettore di stato di base $\ket{i}$, lo stato del qubit 2 corrisponde alla cifra più a sinistra. Ad esempio, $\ket{6} = \ket{110}$ include qubit 2 e 1 in $\ket{1}$ e qubit 0 in $\ket{0}$.

Le altre righe descrivono l'ampiezza della probabilità di misurare il vettore di stato di base $\ket{n}$ in formato sia cartesiano che polare. Esame della prima riga per lo stato di input $\ket{000}$:

  • |0>: corrisponde allo stato di base computazionale 0 (dato che lo stato iniziale dopo l'allocazione era $\ket{000}$, a questo punto si prevede che sia l'unico stato con ampiezza di probabilità).
  • 1.000000 + 0.000000 i: ampiezza della probabilità in formato cartesiano.
  • ==: il segno equal separa entrambe le rappresentazioni equivalenti.
  • ********************: rappresentazione grafica della grandezza. Il numero di * è proporzionale alla probabilità di misurare questo vettore di stato.
  • [ 1.000000 ]: valore numerico della grandezza.
  • ---: rappresentazione grafica della fase dell'ampiezza.
  • [ 0.0000 rad ]: valore numerico della fase (in radianti).

Sia la grandezza che la fase vengono visualizzate con una rappresentazione grafica. La rappresentazione della grandezza è diretta: mostra una barra di * e maggiore è la probabilità, maggiore sarà la barra.

L'output visualizzato mostra che le operazioni programmate hanno trasformato lo stato da

$$ \ket{\psi}_{initial} = \ket{000} $$

in

$$ \begin{align} \ket{\psi}_{final} &= \frac{1}{\sqrt{8}} \left( \ket{000} + \{001}ket + \ket{010} + \ket{011} + \ket + \ket + \ket{100} + \ket{101} + \ket + \ket{110}{111} \right) \\ &= \frac{1}{\sqrt{2^n}}\sum_{j=0}^{2^n-1} \ket{j}, \end{align} $$

che è esattamente il comportamento della trasformata di Fourier a tre qubit.

Se si desidera sapere come sono interessati gli altri stati di input, è consigliabile sperimentarlo applicando altre operazioni di qubit prima della trasformazione.

Aggiungere misurazioni al circuito QFT

Quanto visualizzato dalla funzione DumpMachine ha mostrato i risultati dell'operazione, ma purtroppo un elemento fondamentale della meccanica quantistica stabilisce che un sistema quantistico reale non può avere tale funzione DumpMachine. Al contrario, le informazioni vengono estratte tramite misurazioni, che in generale non solo non forniscono informazioni sullo stato quantistico completo, ma possono anche modificare drasticamente il sistema stesso.

Esistono molti tipi di misurazioni quantistiche, ma questo esempio è incentrato sulle misurazioni più semplici, quelle proiettive su singoli qubit. Al momento della misurazione in una determinata base (ad esempio, la base computazionale $ { \ket{0}, \ket{1} } $), lo stato del qubit viene proiettato su qualsiasi stato di base misurato, di conseguenza elimina qualsiasi sovrapposizione tra i due.

Modificare l'operazione QFT

Per implementare le misurazioni all'interno di un programma Q#, usare l'operazione M, che restituisce un tipo Result.

Prima modificare l'operazione Perform3QubitQFT in modo che restituisca una matrice di risultati della misurazione (Result[] invece di Unit).

operation Perform3QubitQFT() : Result[] {

Definire e inizializzare una matrice Result[]

Prima di allocare i qubit, dichiarare e associare una matrice a tre elementi (una Result per ogni qubit):

mutable resultArray = [Zero, size = 3];

La parola chiave mutable che precede resultArray consente di modificare la variabile in un secondo momento nel codice, ad esempio quando si aggiungono i risultati della misurazione.

Eseguire misurazioni in un ciclo for e aggiungere i risultati alla matrice

Dopo le operazioni di trasformazione QFT, inserire il codice seguente:

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

La funzione IndexRange chiamata in una matrice (ad esempio, la matrice di qubit, qs) restituisce un intervallo sugli indici della matrice. In questo caso, viene usato nel ciclo for per misurare in sequenza ogni qubit che usa l'istruzione M(qs[i]). Ogni tipo Result misurato (Zero o One) viene quindi aggiunto alla posizione di indice corrispondente in resultArray con un'istruzione update-and-reassign.

Nota

La sintassi di questa istruzione è univoca per Q#, ma corrisponde alla riassegnazione di variabili simili resultArray[i] <- M(qs[i]) vista in altri linguaggi, ad esempio F# e R.

La parola chiave set viene sempre usata per riassegnare le variabili associate tramite mutable.

Restituire resultArray

Con tutti e tre i qubit misurati e i risultati aggiunti a resultArray, è possibile reimpostare e deallocare i qubit come prima in modo sicuro. Per restituire le misurazioni, inserire:

return resultArray;

Eseguire il circuito QFT con le misurazioni

Ora modificare la posizione delle funzioni DumpMachine in modo da restituire lo stato prima e dopo le misurazioni. Il codice Q# finale avrà un aspetto simile al seguente:

namespace NamespaceQFT {
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Diagnostics;
    open Microsoft.Quantum.Math;
    open Microsoft.Quantum.Arrays;

    operation Perform3QubitQFT() : 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) {
            set resultArray w/= i <- M(qs[i]);
        }

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

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

    }
}

Suggerimento

Ricordarsi di salvare il file ogni volta che si introduce una modifica al codice prima di eseguirlo di nuovo.

  1. Aggiungere un oggetto EntryPoint prima dell'operazione Perform3qubitQFT :

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. Impostare il target profilo su Senza restrizioni. Fare clic sul pulsante QIR: Base nella parte inferiore della finestra di VS Code e selezionare Senza restrizioni dal menu a discesa. Se il target profilo non è impostato su Senza restrizioni, verrà visualizzato un errore quando si esegue il programma.

  3. Per eseguire il programma, selezionare Esegui Q# file dall'icona di riproduzione nell'elenco a discesa in alto a destra oppure premere CTRL+5. Il programma esegue l'operazione o la funzione contrassegnata con l'attributo @EntryPoint() nel simulatore predefinito.

  4. Gli Message output e DumpMachine vengono visualizzati nella console di debug.

L'output dovrebbe essere simile all'output:

Before measurement: 
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|1>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|2>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|3>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|4>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|5>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|6>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
|7>:     0.353553 +  0.000000 i  ==     ***                  [ 0.125000 ]     --- [  0.00000 rad ]
After measurement:
# wave function for qubits with ids (least to most significant): 0;1;2
|0>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|1>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|2>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|3>:     1.000000 +  0.000000 i  ==     ******************** [ 1.000000 ]     --- [  0.00000 rad ]
|4>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|5>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|6>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]
|7>:     0.000000 +  0.000000 i  ==                          [ 0.000000 ]

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

Questo output illustra alcuni aspetti diversi:

  1. Confrontando il risultato restituito con la pre-misurazione DumpMachine, è chiaro che non viene illustrata la sovrapposizione post-QFT agli stati di base. Una misurazione restituisce solo uno stato di base, con una probabilità determinata dall'ampiezza di tale stato nella funzione d'onda del sistema.
  2. Dalla post-misurazione DumpMachine si può notare che la misurazione cambia lo stato stesso, proiettandolo dalla sovrapposizione iniziale agli stati di base al singolo stato di base corrispondente al valore misurato.

Ripetendo questa operazione più volte, si noterà che le statistiche dei risultati inizieranno a illustrare la sovrapposizione ugualmente ponderata dello stato post-QFT, che dà origine a un risultato casuale per ogni esecuzione. Tuttavia, oltre a essere inefficiente e comunque imperfetta, questa operazione riprodurrebbe solo le ampiezze relative degli stati di base, non le fasi relative tra di loro. Quest'ultimo non è un problema in questo esempio, ma si noterebbe che vengono visualizzate fasi relative se per il QFT venisse fornito un input più complesso rispetto a $\ket{000}$.

Usare le Q# operazioni per semplificare il circuito QFT

Come accennato nell'introduzione, gran parte della potenza di Q# è basata sul fatto che consente di astrarre i problemi di gestione dei singoli qubit. In effetti, se si desidera sviluppare programmi quantistici applicabili e su larga scala, la preoccupazione che un'operazione H venga eseguita prima o dopo una determinata rotazione potrebbe solo rallentare l'utente.

Lo Q# spazio dei nomi Microsoft.Quantum.Canon contiene l'operazione ApplyQFT , che è possibile usare e applicare per qualsiasi numero di qubit.

  1. Per accedere all'operazione, aggiungere l'istruzione ApplyQFT per lo Microsoft.Quantum.Canon spazio dei nomi all'inizio del Q# file:open

    open Microsoft.Quantum.Canon;
    
  2. Sostituire tutti gli elementi dal primo H al SWAP sostituito da:

    ApplyQFT(qs);
    
  3. Eseguire di nuovo il Q# programma e notare che l'output è uguale a prima.

  4. Per visualizzare il vantaggio reale dell'uso Q# delle operazioni, modificare il numero di qubit in un valore diverso da 3:

mutable resultArray = [Zero, size = 4];

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

È quindi possibile applicare il QFT appropriato per un determinato numero di qubit, senza doversi preoccupare della confusione di nuove operazioni H e rotazioni su ogni qubit.

Passaggi successivi

Esplorare altre esercitazioni su Q#: