Blog ENI : Toute la veille numérique !
En raison d'une opération de maintenance, le site Editions ENI sera inaccessible le mardi 10 décembre, en début de journée. Nous vous invitons à anticiper vos achats. Nous nous excusons pour la gêne occasionnée
En raison d'une opération de maintenance, le site Editions ENI sera inaccessible le mardi 10 décembre, en début de journée. Nous vous invitons à anticiper vos achats. Nous nous excusons pour la gêne occasionnée
  1. Livres et vidéos
  2. Informatique quantique
  3. Le langage Q# et son utilisation
Extrait - Informatique quantique De la physique quantique à la programmation quantique en Q#
Extraits du livre
Informatique quantique De la physique quantique à la programmation quantique en Q#
3 avis
Revenir à la page d'achat du livre

Le langage Q# et son utilisation

Présentation du langage Q#

1. Introduction

Q# (« Q Sharp ») est le langage de programmation inventé par Microsoft et dédié à l’informatique quantique. Il peut s’exécuter sur des machines quantiques ou sur des simulateurs quantiques. Il est sorti en 2017 dans sa version 0.1. Il est présenté dans la documentation officielle comme un langage évolutif et multiparadigme.

2. Un langage évolutif

Il est évolutif en premier lieu, car il est sujet à des évolutions syntaxiques majeures, au moins tant qu’il n’a pas atteint la version 1.0. La dernière version du langage, lors de l’écriture de ce livre, date de fin octobre 2018 et correspond à la version 0.3.

Dans la documentation officielle, il est présenté comme évolutif pour une seconde raison : il peut être utilisé sur de très petites machines quantiques de quelques bits quantiques, tout comme sur d’énormes machines qui compteront peut-être un jour des milliers de bits quantiques, voire beaucoup plus.

3. Un langage multiparadigme

En programmation informatique, un paradigme de programmation correspond à la « philosophie » de codage associée au langage considéré. Il existe plusieurs paradigmes de programmation, en particulier la programmation impérative...

Éléments relatifs aux signes de ponctuation

1. Contexte

La documentation officielle du langage précise que depuis la version 0.3 (fin octobre 2018), les programmeurs du langage Q# cherchent absolument à clarifier l’usage de certains signes comme la virgule ou le point-virgule dans le langage. En effet, il pouvait arriver que ces signes de ponctuation voient leurs significations changer selon le contexte ce qui entretenait une certaine ambiguïté.

2. Nomenclature relative aux signes de ponctuation

Depuis la version 0.3, le sens donné à chaque signe de ponctuation en Q# n’est plus soumis au contexte et est supposé être toujours le même. Concrètement, on a la nomenclature suivante :

  • Le point-virgule est désormais utilisé exclusivement pour terminer une instruction en fin de ligne.

  • La virgule permet de structurer entre eux les éléments de tableaux, les arguments au sein d’une liste, etc. Elle a exclusivement une fonction de séparateur dans tout ce qui s’apparente à une liste.

  • Le deux-points est utilisé exclusivement pour annoter un type, notamment dans la signature des fonctions.

Le deux-points n’est donc pas utilisable dans les ternaires comme en C# par exemple. Pour cet usage, on utilise le signe Pipe « | » :

En C# :

bool chaud = (température >= 25) ? true  : false;...

Les types en Q#

1. Les types primitifs du langage

Plusieurs types primitifs sont disponibles en Q# :

  • Le type entier Int représente une valeur entière codée sur 64 bits.

  • Le type Double représente une valeur flottante en double précision.

  • Le type booléen Bool est égal soit à true soit à false.

  • Le type Qubit représente un bit quantique. Conformément à la physique quantique, une instance de Qubit est initialisée et à un moment donné, est mesurable. Par contre, entre ces deux évènements, le Qubit considéré et son état quantique courant ne sont pas maîtrisés et peuvent essentiellement être transmis pour diverses transformations comme le placer en entrée de portes quantiques.

  • Le type Pauli est également un type primitif en Q#. Il correspond clairement aux diverses portes quantiques dites de Pauli étudiées dans les chapitres précédents. Il correspond donc aux diverses rotations possibles dans la sphère de Bloch, respectivement autour des axes X, Y, et Z. Ce type est une énumération et comprend donc chacune des trois rotations précitées ainsi qu’une transformation de type identité. Ces quatre valeurs d’énumération sont :

  • PauliX

  • PauliY

  • PauliZ

  • PauliI

  • Le type Result correspond très précisément...

Les structures de données

1. Introduction

On distingue plusieurs structures de données en Q# et plus généralement plusieurs types de conteneurs à même de stocker une information structurée. Il s’agit notamment des tableaux et des tuples que la présente partie se propose de détailler.

2. Le tableau

Cette structure de données utilise les caractères crochets, ([]) un peu sur le modèle d’autres langages comme le C#. Ainsi, l’exemple suivant correspond à un tableau de bits quantiques à une dimension :

Qubit[] 

Comme dans d’autres langages et comme dans l’exemple de téléportation abordé dans le chapitre précédent, on peut donner une taille à ce tableau. Ci-dessous un registre de deux bits quantiques :

Qubit[2] 

On peut également définir des tableaux multidimensionnels par exemple de dimension deux, comme ci-dessous :

Qubit[][] 

3. Le tuple

Un tuple est selon le Wiktionnaire dans sa définition au 14 novembre 2018, une « collection ordonnée de valeurs d’un nombre indéfini d’attributs relatifs à un même objet ». C’est exactement cette approche qu’il faut retenir pour envisager ce que sont les tuples dans le langage Q#.

On utilise pour définir un tuple les parenthèses et la virgule comme séparateur....

Considérations sur les types et les structures de données en Q#

1. Définir ses propres types en Q#

Un mot-clé permet de créer de nouveaux types en Q#. Il s’agit du mot-clé newType. On peut l’utiliser ainsi, au sein d’un tuple par exemple :

newtype monType = (Int, Qubit); 

Ce type monType permet de créer des instances associant un Int et un Qubit. Le support de ce type est un tuple : ce nouveau type sera donc immutable. C’est-à-dire qu’une fois l’instance créée on ne pourra pas la modifier.

À noter qu’il faut évidemment se méfier des références circulaires dans la création des types. Le code suivant entraîne irrémédiablement une erreur de compilation :

newtype monType = (Int, monType_2); 
newtype monType_2 = (Qubit, monType); 

Ces types définis par l’utilisateur sont concernés par un important changement de la version 0.3. En effet, et jusqu’à cette version, un type utilisateur était un type à part entière sans spécificité. Depuis la version 0.3 incluse, un type utilisateur est un type encapsulé (wrapped). Un nouveau symbole est lié à ces types encapsulés qui permet de les désencapsuler (unwraped). Cette nouveauté de la version 0.3 du langage fait entièrement l’objet...

Opérations et fonctions Q#

1. Distinction entre opération et fonction en Q#

Les deux notions peuvent être vues comme des sous-programmes. La différence se situe au niveau même des actions réalisées par ces sous-programmes :

  • Une opération Q# contient exclusivement des actions quantiques. À ce titre, une opération est un sous-programme quantique.

  • Une fonction Q# est un sous-programme classique. Il peut faire un certain nombre d’actions sauf des actions quantiques. Il ne peut pas manipuler des bits quantiques et encore moins appeler des opérations. On peut néanmoins lui transmettre des opérations en paramètres d’entrées ou de sorties.

Schématiquement :

  • Une opération Q# s’occupe de la partie quantique.

  • Une fonction Q# s’occupe de tout sauf de la partie quantique.

En termes de vocabulaire, opérations et fonctions sont regroupées dans une famille nommée callables (« appelables » en français).

2. Utiliser des « callables » dans des « callables »

Précisons l’écriture des signatures de ces callables quand elles sont utilisées « inline », c’est-à-dire dans la définition d’une autre callable.

Les deux séquences de caractères qui sont utilisées pour cela sont :

  • =>

  • ->

La première est utilisée pour les opérations et la seconde pour les fonctions.

Par exemple, voici la signature d’une opération dans laquelle apparaissent les entrées et les sorties :

((Qubit, Pauli) => Result) 

L’exemple suivant, d’une signature d’opération, est directement issue de l’exemple fourni par Microsoft (issu du fichier MultiControlledNOTTests.qs) et met en évidence l’usage de => :

operation ControlledTestHelper (op : ((Qubit[], Qubit) => 
Unit : Adjoint), target : Qubit[]) : Unit 

L’opération ControlledTestHelper prend en entrée un tuple composé d’une opération op et d’un registre de bits quantiques target. Sa sortie est un tuple vide de type Unit.

Prenons à présent un exemple...

Les variables en Q#

1. Le mot-clé let et l’immutabilité

Le mot-clé let permet de définir une variable immutable qui va être une variable locale. C’est-à-dire qu’elle a une consistance dans le scope courant, que ce soit le corps (body) d’une opération ou encore dans une boucle for par exemple.

Dans l’exemple suivant, on déclare un tableau de portes quantiques. En l’occurrence, la collection des portes de Pauli (X, Y, Z et identité) :

let pauliGates = [PauliX, PauliY, PauliX, PauliI]; 

Il n’y a donc pas de typage explicite avec let : c’est le compilateur qui gère le type selon la valeur affectée.

2. Les mots-clés mutable et set

Se pose donc la question de la création de variables locales qui seraient mutables et donc modifiables a posteriori. Pour répondre à ce besoin le langage Q# offre un mot-clé mutable qui permet de créer une variable mutable par la suite modifiable grâce au mot-clé set.

Dans le code suivant, on déclare une fonction qui prend en paramètre un entier taille. On souhaite allouer un tableau d’entiers. Dans chaque case du tableau, on désire stocker le carré de l’index de la case.

Si taille = 6 : on désire renvoyer le tableau [ 0, 1, 4, 9, 16, 25].

Si on crée le tableau avec let, il sera immutable et on ne pourra...

Manipuler des bits quantiques en Q#

1. Utiliser un registre de bits quantiques

Comme nous l’avons entraperçu plus tôt, il est d’usage d’utiliser using pour créer et utiliser en mémoire un registre de bits quantiques. Dans l’exemple suivant, on utilise un registre de trois bits quantiques :

using (register = Qubit[3]) { 
   // On fait quelque-chose 
} 
Lorsqu’il est initialisé, un bit quantique est à la valeur images/eq56.PNG c’est-à-dire le vecteur (1 0) ou exprimé en Q# la valeur Zero (par opposition à One). Ici, l’initialisation correspond à quelque-chose qui peut s’écrire images/eq204.png, c’est-à-dire que chaque bit quantique qui compose le registre est à la valeur Zero et donc représenté par le vecteur (1 0).

2. Première approche des portes quantiques en Q#

a. Les primitives en Q#

Les primitives correspondent ni plus ni moins aux portes quantiques étudiées précédemment dans le livre. Elles sont au cœur du fonctionnement des opérations, ces callables en charge de la partie quantique, par opposition au rôle des fonctions.

On en a déjà entraperçu certaines et notamment les portes à un bit quantique en entrée comme X, Y et Z qui correspondent aux portes quantiques Pauli-X (NOT), Pauli-Y et Pauli-Z. Nous avons également vu H, la porte de Hadamard.

L’exemple suivant, librement inspiré de la documentation officielle, propose de procéder à l’application de la porte quantique de Pauli-X à chaque bit quantique d’un registre quantique dont certains bits quantiques sont initialisés à One et d’autres à Zero.

operation PreparationRegistre(bitstring : Bool[], registre : 
Qubit[]) : Unit { 
   body (...) { 
       let taille = Length(registre); 
       for (ii in 0..taille - 1) { 
           if (bitstring[ii]) { 
               X(registre[ii]); 
           } 
       } 
   } 
   adjoint auto; 
   controlled auto; 
   controlled adjoint auto; 
} ...

Tester son code quantique et le déboguer

1. Les tests unitaires en Q#

a. Contexte

Il est possible de procéder à des tests unitaires en Q#, mais pour cela il est nécessaire de créer un projet dédié exactement comme on le ferait avec un autre langage. Cette création d’un projet de tests unitaires (Xunit) va dépendre de l’environnement de développement choisi, Microsoft Visual Studio ou Visual Studio Code.

b. Création d’un projet de tests avec Microsoft Visual Studio

Pour créer un projet au sein de la solution courante, suivez cette procédure :

 Ouvrez Microsoft Visual Studio.

 Ouvrez le menu Fichier puis sélectionnez Nouveau - Projet.

 Sélectionnez le template « Q# Test » et procédez à la création du projet.

c. Création d’un projet de tests avec Visual Studio Code

La création d’un projet de test se fait avec Visual Studio Code en ligne de commandes. Dans votre terminal, que ce soit sous Windows, Linux ou Mac OS.

Commencez par exécuter cette ligne pour créer le projet de test :

dotnet new xunit -lang Q 

Accédez au répertoire Tests tout juste créé :

cd Tests 

Ouvrez Visual Studio Code :

code . 

d. Écrire des tests unitaires

À ce stade, et quel que soit votre environnement de développement, votre solution inclut un projet de tests qui lui-même inclut deux fichiers, le premier d’extension Q# et le second d’extension C# :

1.

Tests.qs

2.

TestSuiteRunner.cs

Schématiquement, le premier fichier (.qs) va inclure les tests unitaires. Le second fichier (.cs) permet d’exécuter les tests du premier et d’en tirer un rapport de tests par exemple.

Le fichier Tests.qs contient un premier test unitaire d’exemple nommé AllocateQubitTest qui consiste à vérifier qu’un bit quantique tout juste créé est effectivement disponible avec un état quantique équivalent à images/eq56.PNG.
operation AllocateQubitTest () : Unit 
{ 
   body (...) 
   { 
       using (qs = Qubit())  
       { 
           Assert([PauliZ], [qs], Zero, "Newly allocated qubit must...

Inventaire des espaces de noms quantiques

1. Contexte

Les espaces de noms composant le Microsoft Quantum Development Kit ont été évoqués à plusieurs reprises dans le présent chapitre. Il s’agit à présent de préciser le rôle et le contenu des principaux espaces de noms de la solution quantique de Microsoft.

2. L’espace de noms Microsoft.Quantum.Core

L’espace de noms inclut les principales fonctions qui permettent de manipuler des structures de données et notamment les énumérations de type Range. Par exemple, les fonctions suivantes :

  • Length (taille d’un tableau)

  • RangeEnd

  • RangeReverse

  • RangeStart

  • RangeStep

Des éléments de documentation sur cet espace de noms se trouvent à cette adresse : https://docs.microsoft.com/en-us/qsharp/api/prelude/microsoft.quantum.core

3. Les espaces Microsoft.Extensions.*

Microsoft.Extensions inclut les espaces de noms suivants :

  • Microsoft.Extensions.Bitwise

  • Microsoft.Extensions.Convert

  • Microsoft.Extensions.Diagnostics

  • Microsoft.Extensions.Math

  • Microsoft.Extensions.Testing

a. Microsoft.Extensions.Bitwise

Cet espace de noms inclut l’ensemble des opérateurs logiques, relatifs à l’algèbre de Boole. Par exemple, elle inclut notamment les fonctions suivantes :

  • And

  • Not

  • Or

  • Parity

  • Xbits

  • Xor

  • Zbits

Des éléments de documentation sur cet espace de noms se trouvent...

C# en pilotage du simulateur quantique

Si la simulation quantique est assurée par le code Q# dont les fichiers sont d’extension .qs, il est nécessaire de piloter cette simulation. Ceci est assuré par le code C# dont le fichier est d’extension .cs. Que fait-il ? Il va instancier le simulateur quantique, lui donner du travail et recevoir en retour le résultat.

Le simulateur quantique est défini dans l’espace de noms Microsoft.Quantum.Simulation.Simulators. On utilise en première approche le simulateur quantique nommé QuantumSimulator dont le code est rappelé plus loin. Voici comment lancer une simulation quantique :

 using (var sim = new QuantumSimulator()) 
{ 
       var res = myOperation.Run(sim).Result; 
} 
  

Code de QuantumSimulator : 

   public class QuantumSimulator : SimulatorBase, Idisposable 
   { 
       public const string QSIM_DLL_NAME = 
"Microsoft.Quantum.Simulator.Runtime.dll"; 
       public QuantumSimulator(bool throwOnReleasingQubitsNotInZeroState = 
true, uint? randomNumberGeneratorSeed = null, bool disableBorrowing = 
false); 
 
       public uint Seed { get; } 
       public uint Id { get; } 
       public override string Name { get; } 
 
       public void Dispose(); 
 
       public class QSimX : X 
       { 
           public QSimX(QuantumSimulator m); 
 
           public override Func<Qubit, QVoid> Body { get; } 
           public override Func<(QArray<Qubit>, Qubit), QVoid> 
ControlledBody { get; } 
       } 
       public class QSimT : T 
       { 
           public QSimT(QuantumSimulator m); 
 
           public override Func<Qubit, QVoid> Body { get; } 
     ...