Tutoriel : Implémenter la transformation de Fourier quantique dans Q#

Notes

Quantum Development Kit Microsoft (QDK classique) ne sera plus pris en charge après le 30 juin 2024. Si vous êtes un développeur QDK existant, nous vous recommandons de passer au nouvel Azure Quantum Development Kit (QDK moderne) pour continuer à développer des solutions quantiques. Pour plus d’informations, consultez Migrer votre Q# code vers le QDK moderne.

Ce tutoriel vous montre comment écrire et simuler un programme quantique de base qui fonctionne sur des qubits individuels.

Même si Q# a principalement été créé comme un langage de programmation élémentaire destiné à des programmes quantiques à grande échelle, il peut aussi s’utiliser pour explorer la programmation quantique de manière plus approfondie, à savoir en traitant directement des qubits spécifiques. Plus précisément, ce tutoriel examine de plus près la transformation de Fourier quantique (QFT), une sous-routine qui fait partie intégrante de nombreux algorithmes quantiques plus grands.

Ce didacticiel vous montre comment effectuer les opérations suivantes :

  • Définissez des opérations quantiques dans Q#.
  • Écrire le circuit de transformation de Fourier quantique
  • Simuler une opération quantique de l’allocation de qubits à la sortie de mesure.
  • Observez comment la fonction d’onde simulée du système quantique évolue tout au long de l’opération.

Notes

Cette vue plus approfondie du traitement des informations quantiques est souvent décrite par le terme de circuits quantiques, qui représentent l’application séquentielle de portes, ou opérations, à des qubits spécifiques d’un système. Ainsi, les opérations mono- et multi-qubit que vous appliquez séquentiellement peuvent être facilement représentées dans des schémas de circuit. Par exemple, la transformation de Fourier quantique à trois qubits complète utilisée dans ce didacticiel a la représentation suivante en tant que circuit : Diagramme d’un circuit de transformation de Fourier quantique.

Conseil

Si vous souhaitez accélérer votre parcours d’informatique quantique, case activée code avec Azure Quantum, une fonctionnalité unique du site web Azure Quantum. Ici, vous pouvez exécuter des exemples intégrés Q# ou vos propres Q# programmes, générer un nouveau Q# code à partir de vos invites, ouvrir et exécuter votre code dans VS Code pour le web en un seul clic, et poser des questions à Copilot sur l’informatique quantique.

Prérequis

Create un nouveau Q# fichier

  1. Dans VS Code, sélectionnez Fichier > Nouveau fichier texte.
  2. Enregistrez le fichier sous QFTcircuit.qs. Ce fichier contient le Q# code de votre programme.
  3. Ouvrez QFTcircuit.qs.

Écrire un circuit QFT dans Q#

La première partie de ce tutoriel consiste à définir l’opération Q#Perform3qubitQFT, qui exécute la transformation de Fourier quantique sur trois qubits. La fonction DumpMachine est utilisée pour observer la façon dont la fonction d’onde simulée du système à trois qubits évolue au fil de l’opération. Dans la deuxième partie du tutoriel, vous ajouterez des fonctionnalités de mesure et vous comparerez les états des qubits avant et après les mesures.

Vous construirez l’opération étape par étape. Copiez et collez le code des sections suivantes dans le fichier QFTcircuit.qs .

Vous pouvez afficher le code complet Q# de cette section en tant que référence.

Espaces de noms pour accéder à d’autres opérations Q#

Dans votre fichier Q#, définissez l’espace de noms NamespaceQFT, auquel le compilateur a accès. Pour que cette opération utilise des opérations Q# existantes, ouvrez les espaces de noms Microsoft.Quantum.* appropriés.

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

    // operations go here
}

Définir des opérations avec des arguments et des retours

Ensuite, définissez l’opération Perform3qubitQFT :

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

Pour le moment, l’opération n’accepte aucun argument et elle retourne un objet Unit, ce qui revient à retourner void en C#, ou un tuple vide, Tuple[()], en Python. Plus tard, vous modifierez l’opération afin qu’elle retourne un tableau des résultats de mesure.

Allouer des qubits

Dans l’opérationQ#, allouez un registre de trois qubits avec le use mot clé. Avec use, les qubits sont automatiquement alloués dans l’état $\ket{0}$.

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

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

De la même façon que dans les calculs quantiques réels, Q# ne vous permet pas d’accéder directement aux états des qubits. Toutefois, l’opération DumpMachine imprime l’état actuel de la target machine, de sorte qu’elle peut fournir des informations précieuses pour le débogage et l’apprentissage lorsqu’elle est utilisée conjointement avec le simulateur d’état complet.

Appliquer un qubit et des opérations contrôlées

Ensuite, vous appliquez les opérations qui composent l’opération Perform3qubitQFT elle-même. Q# contient déjà ces opérations et de nombreuses autres opérations quantiques de base dans l’espace de noms Microsoft.Quantum.Intrinsic.

L’opération H (de Hadamard) est la première opération appliquée au premier qubit :

Diagramme montrant un circuit pour trois qubits QFT jusqu’au premier Hadamard.

Pour appliquer une opération à un qubit spécifique à partir d’un registre (par exemple, un seul Qubit issu d’un tableau Qubit[]), utilisez la notation d’index standard. Donc, l’application de l’opération H au premier qubit du registre qs s’écrit ainsi :

H(qs[0]);

En plus d’appliquer l’opération H à des qubits individuels, le circuit QFT consiste principalement en des rotations R1 contrôlées. Une R1(θ, <qubit>) opération en général laisse le composant $\ket{0}$ du qubit inchangé lors de l’application d’une rotation de $e^{i\theta}$ au composant $\ket{1}$.

Q# permet de conditionner facilement l’exécution d’une opération sur un ou plusieurs qubits de contrôle. En général, il suffit de faire précéder l’appel par Controlled pour que les arguments d’opération changent comme suit :

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

Notez que l’argument de qubit de contrôle doit être un tableau, même s’il référence un seul qubit.

Les opérations contrôlées dans le QFT sont les R1 opérations qui agissent sur le premier qubit (et contrôlées par les deuxième et troisième qubits) :

Diagramme montrant un circuit pour la transformation de fourier quantique à trois qubits via le premier qubit.

Dans votre fichier Q#, appelez ces opérations avec ces instructions :

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

La fonction PI() est utilisée pour définir les rotations en termes de pi radians.

Appliquer l’opération SWAP

Après avoir appliqué les opérations pertinentes H et les rotations contrôlées aux deuxième et troisième qubits, le circuit se présente comme suit :

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

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

Enfin, vous appliquez une SWAP opération aux premier et troisième qubits pour terminer le circuit. Celle-ci est nécessaire, car la nature de la transformation de Fourier quantique retourne les qubits dans l’ordre inverse, de sorte que les échanges permettent une intégration continue de la sous-routine dans des algorithmes plus importants.

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

Vous avez maintenant terminé d’écrire les opérations au niveau qubit de la transformation de Fourier quantique dans l’opération Q# :

Diagramme montrant un circuit pour la transformation de fourier quantique à trois qubits.

Libérer des qubits

La dernière étape consiste à appeler DumpMachine() une nouvelle fois pour voir l’état après l’opération, puis à libérer les qubits. Les qubits étaient dans l’état $\ket{0}$ lorsque vous les avez alloués. Vous devez maintenant les rétablir à leur état initial en utilisant l’opération ResetAll.

Exiger que tous les qubits soient explicitement réinitialisés à $\ket{0}$ est une fonctionnalité de base de Q#, car elle permet à d’autres opérations de connaître leur état précisément lorsqu’elles commencent à utiliser ces mêmes qubits (une ressource rare). En outre, cela garantit qu’ils ne sont pas intriqués dans d’autres qubits dans le système. Si la réinitialisation n’est pas effectuée à la fin d’un bloc d’allocation use, une erreur d’exécution peut être générée.

Ajoutez les lignes suivantes à votre fichier Q# :

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

ResetAll(qs); // deallocate qubits

L’opération QFT complète

Le Q# programme est terminé. Votre fichier QFTcircuit.qs doit maintenant ressembler à ceci :

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

    }
}

Exécuter le circuit QFT

Pour l’instant, l’opération Perform3qubitQFT ne retourne aucune valeur : l’opération retourne Unit une valeur. Plus tard, vous modifierez l’opération pour retourner un tableau de résultats de mesure (Result[]).

  1. Lors de l’exécution d’un Q# programme, vous devez ajouter un EntryPoint au Q# fichier. Cet attribut indique au compilateur que cette opération est le point d’entrée du programme. Ajoutez la ligne suivante en haut de votre Q# fichier avant l’opération Perform3qubitQFT :

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. Avant d’exécuter le programme, vous devez définir le profil sur targetNon restreint. Sélectionnez Affichage -> Palette de commandes, recherchez QIR, sélectionnez Q#: Définir le profil QIR target Azure Quantum, puis sélectionnez Q#: non restreint.

  3. Pour exécuter votre programme, sélectionnez Exécuter Q# le fichier dans la liste déroulante icône de lecture en haut à droite, ou appuyez sur Ctrl+F5. Le programme exécute l’opération ou la fonction marquée avec l’attribut @EntryPoint() sur le simulateur par défaut.

  4. Les Message sorties et DumpMachine s’affichent dans la console de débogage.

Notes

Si le target profil n’est pas défini sur Non restreint, une erreur s’affiche lorsque vous exécutez le programme.

Comprendre la sortie du circuit QFT

En cas d’appel sur le simulateur d’état complet, DumpMachine() fournit ces représentations multiples de la fonction d’onde de l’état quantique. Les états possibles d’un système à $n$ qubits peuvent être représentés par $2^n$ états de base de calcul, chacun avec un coefficient complexe correspondant (une amplitude et une phase). Les états de base de calcul correspondent à toutes les chaînes binaires possibles de longueur $n$, autrement dit, à toutes les combinaisons possibles des états de qubit $\ket{0}$ et $\ket{1}$, où chaque chiffre binaire correspond à un qubit individuel.

La première ligne propose un commentaire avec les ID des qubits correspondants dans leur ordre significatif. Le qubit 2 est le « plus significatif » ; cela signifie que, dans la représentation binaire du vecteur d’état de base $\ket{i}$, l’état du qubit 2 correspond au chiffre le plus à gauche. Par exemple, $\ket{6} = \ket{110}$ comprend les qubits 2 et 1 à la fois dans $\ket{1}$ et le qubit 0 dans $\ket{0}$.

Les autres lignes décrivent l’amplitude de probabilité de la mesure du vecteur d’état de base $\ket{i}$ à la fois dans les formats cartésien et polaire. Examinez la première ligne pour l’état d’entrée $\ket{000}$ :

  • |0>: Cette ligne correspond à l’état de base de calcul 0 (étant donné que l’état initial post-allocation était $\ket{000}$, il est censé être le seul état avec une amplitude de probabilité à ce stade).
  • 1.000000 + 0.000000 i : amplitude de probabilité au format cartésien.
  • == : le signe equal sépare deux représentations équivalentes.
  • ******************** : représentation graphique de la magnitude. Le nombre de * est proportionnel à la probabilité de mesurer ce vecteur d’état.
  • [ 1.000000 ] : valeur numérique de la magnitude.
  • --- : Représentation graphique de la phase de l’amplitude.
  • [ 0.0000 rad ] : valeur numérique de la phase (en radians).

La magnitude et la phase sont toutes les deux affichées avec une représentation graphique. La représentation de la magnitude est simple : elle montre une barre de * et plus la probabilité est élevée, plus la barre est longue.

La sortie affichée illustre que les opérations programmées ont transformé l’état à partir de

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

to

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

ce qui correspond précisément au comportement de la transformation de Fourier à trois qubits.

Si vous êtes curieux de savoir comment les autres états d’entrée sont impactés, vous pouvez essayer d’appliquer d’autres opérations qubit avant la transformation.

Ajouter des mesures au circuit QFT

L’affichage de la fonction DumpMachine a montré les résultats de l’opération, mais malheureusement, un principe fondamental de la mécanique quantique veut qu’un système quantique réel ne peut pas avoir une telle fonction DumpMachine. Au lieu de cela, les informations sont extraites par le biais de mesures, ce qui, en général, ne permet pas de fournir des informations sur l’état quantique complet, mais peut également altérer radicalement le système en lui-même.

Il existe de nombreux types de mesures quantiques, mais l’exemple étudié ici se concentre sur les plus basiques, à savoir les mesures projectives sur des qubits uniques. En cas de mesure dans une base donnée (par exemple, la base de calcul $ { \ket{0}, \ket{1} } $), l’état du qubit est projeté sur tout état de base qui a été mesuré, détruisant ainsi toute superposition entre les deux.

Modifier l’opération QFT

Pour implémenter des mesures dans un programme Q#, utilisez l’opération M, qui retourne un type Result.

Tout d’abord, modifiez l’opération Perform3QubitQFT pour retourner un tableau de résultats de mesure, Result[], au lieu de Unit.

operation Perform3QubitQFT() : Result[] {

Définir et initialiser un tableau Result[]

Avant d’allouer des qubits, déclarez et liez un tableau à trois éléments (un Result pour chaque qubit) :

mutable resultArray = [Zero, size = 3];

Le mot clé mutable qui précède resultArray permet à la variable d’être modifiée dans le code plus tard, par exemple, lors de l’ajout des résultats de vos mesures.

Effectuer des mesures dans une boucle for et ajouter des résultats au tableau

Après les opérations de transformation QFT, insérez le code suivant :

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

La fonction IndexRange appelée sur un tableau (par exemple, le tableau de qubits, qs) retourne une plage sur les index du tableau. Ici, il est utilisé dans la boucle for pour mesurer séquentiellement chaque qubit avec l’instruction M(qs[i]). Chaque type Result mesuré (soit Zero, soit One) est ensuite ajouté à la position d’index correspondante dans resultArray avec une instruction de mise à jour et réaffectation.

Notes

La syntaxe de cette instruction est propre à Q#, mais elle correspond à la réaffectation de variables similaires resultArray[i] <- M(qs[i]), vue dans d’autres langages comme F# et R.

Le mot clé set est toujours utilisé pour réaffecter des variables liées à l’aide de mutable.

Retourner resultArray

Avec les trois qubits mesurés et les résultats ajoutés à resultArray, vous pouvez sereinement réinitialiser et libérer les qubits comme auparavant. Pour retourner les mesures, insérez :

return resultArray;

Exécuter le circuit QFT avec les mesures

Changez maintenant le placement des DumpMachine pour sortir l’état avant et après les mesures. Votre code Q# final doit ressembler à ceci :

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;

    }
}

Conseil

Rappelez-vous d’enregistrer votre fichier chaque fois que vous apportez une modification au code avant de l’exécuter à nouveau.

  1. Ajoutez un EntryPoint avant l’opération Perform3qubitQFT :

    @EntryPoint()
    operation Perform3qubitQFT() : Unit {
    
  2. Définissez le profil sur targetNon restreint. Cliquez sur le bouton QIR : Base en bas de la fenêtre VS Code et sélectionnez Non restreint dans le menu déroulant. Si le target profil n’est pas défini sur Non restreint, une erreur s’affiche lorsque vous exécutez le programme.

  3. Pour exécuter votre programme, sélectionnez Exécuter Q# le fichier dans la liste déroulante icône de lecture en haut à droite, ou appuyez sur Ctrl+5. Le programme exécute l’opération ou la fonction marquée avec l’attribut @EntryPoint() sur le simulateur par défaut.

  4. Les Message sorties et DumpMachine s’affichent dans la console de débogage.

Votre sortie doit ressembler à la sortie :

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]

Cette sortie illustre plusieurs choses différentes :

  1. En comparant le résultat retourné à la pré-mesure DumpMachine, cela n’illustre clairement pas la superposition post-QFT sur les états de base. Une mesure ne retourne qu’un seul état de base, avec une probabilité déterminée par l’amplitude de cet état dans la fonction d’onde du système.
  2. D’après la post-mesure DumpMachine, vous voyez que la mesure change l’état lui-même, en le projetant de la superposition initiale sur les états de base vers l’état de base unique qui correspond à la valeur mesurée.

Si vous répétez cette opération de nombreuses fois, vous pouvez voir que les statistiques des résultats commencent à illustrer la superposition pondérée de l’état post-QFT qui donne lieu à un résultat aléatoire à chaque fois. Toutefois, en plus d’être inefficace et toujours imparfait, cela ne ferait que reproduire les amplitudes relatives des états de base, et non les phases relatives entre elles. Ce dernier point ne constitue pas un problème dans cet exemple, mais vous verriez des phases relatives apparaître si une entrée plus complexe que $\ket{000}$ était donnée à QFT.

Utiliser les Q# opérations pour simplifier le circuit QFT

Comme mentionné en introduction, la puissance de Q# repose en grande partie sur sa capacité à vous libérer des soucis liés à la gestion des qubits individuels. En effet, si vous voulez développer des programmes quantiques applicables à grande échelle, déterminer si une opération H doit être effectuée avant ou après une rotation particulière ne fait que vous ralentir.

L’espace Q# de noms Microsoft.Quantum.Canon contient l’opération ApplyQFT , que vous pouvez utiliser et appliquer pour n’importe quel nombre de qubits.

  1. Pour accéder à l’opération ApplyQFT , ajoutez open une instruction pour l’espace Microsoft.Quantum.Canon de noms au début du Q# fichier :

    open Microsoft.Quantum.Canon;
    
  2. Remplacez tout, du premier H au SWAP remplacé par :

    ApplyQFT(qs);
    
  3. Réexécutez le Q# programme et notez que la sortie est la même qu’auparavant.

  4. Pour voir l’avantage réel de l’utilisation Q# des opérations, remplacez le nombre de qubits par autre chose que 3:

mutable resultArray = [Zero, size = 4];

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

Vous pouvez ainsi appliquer le QFT approprié pour un nombre donné de qubits, sans avoir à vous soucier des nouvelles opérations H et rotations sur chaque qubit.

Étapes suivantes

Explorez les autres tutoriels Q# :