Déclarations de spécialisation
Comme expliqué dans la section relative aux déclarations appelables, il n’existe actuellement aucune raison de déclarer explicitement des spécialisations pour les fonctions. Cette rubrique s’applique aux opérations et explique comment déclarer les spécialisations nécessaires pour prendre en charge certains foncteurs.
En informatique quantique, il est relativement courant de demander la matrice adjointe d'une transformation donnée. De nombreux algorithmes quantiques nécessitent une opération et sa matrice adjointe pour effectuer un calcul.
Q# utilise le calcul symbolique susceptible de générer automatiquement l’implémentation de la matrice adjointe pour une implémentation de corps particulière. Cette génération est possible même pour les implémentations qui combinent librement des calculs quantiques et classiques. Cela étant, certaines restrictions s’appliquent. Par exemple, pour des raisons de performances, la génération automatique n’est pas prise en charge si l’implémentation utilise des variables mutables. En outre, chaque opération appelée dans le corps génère que la matrice adjointe correspondante et doit prendre en charge le foncteur Adjoint
lui-même.
Même s’il est difficile d’annuler des mesures dans le cas de plusieurs qubits, il n’en reste pas moins possible de combiner des mesures pour que la transformation appliquée soit unitaire. Dans ce cas, cela signifie que, même si l’implémentation du corps contient des mesures qui ne prennent pas en charge le foncteur Adjoint
, le corps dans son intégralité est adjoignable. Pour autant, la génération automatique de l’implémentation de la matrice adjointe échoue. Pour cette raison, il est possible de spécifier manuellement l’implémentation.
Le compilateur génère automatiquement des implémentations optimisées pour les modèles courants tels que les conjugaisons.
Néanmoins, une spécialisation explicite peut être souhaitable pour définir manuellement une implémentation plus optimisée. Il est possible de spécifier explicitement une implémentation et un nombre quelconque d’implémentations.
Notes
L’exactitude d’une telle implémentation spécifiée manuellement n’est pas vérifiée par le compilateur.
Dans l’exemple suivant, la déclaration pour une opération SWAP
, qui échange l’état de deux qubits q1
et q2
, déclare une spécialisation explicite pour sa version adjointe et sa version contrôlée. Alors que les implémentations pour Adjoint SWAP
et Controlled SWAP
sont définies par l’utilisateur, le compilateur doit encore générer l’implémentation pour la combinaison des deux foncteurs (Controlled Adjoint SWAP
, qui est identique à Adjoint Controlled SWAP
).
operation SWAP (q1 : Qubit, q2 : Qubit) : Unit
is Adj + Ctl {
body ... {
CNOT(q1, q2);
CNOT(q2, q1);
CNOT(q1, q2);
}
adjoint ... {
SWAP(q1, q2);
}
controlled (cs, ...) {
CNOT(q1, q2);
Controlled CNOT(cs, (q2, q1));
CNOT(q1, q2);
}
}
Directives de génération automatique
Lorsque vous déterminez la façon de générer une spécialisation particulière, le compilateur hiérarchise les implémentations définies par l’utilisateur. Ainsi, si une spécialisation adjointe est définie par l’utilisateur et qu’une spécialisation contrôlée est générée automatiquement, la spécialisation adjointe contrôlée est générée en fonction de la matrice adjointe définie par l’utilisateur et inversement. Dans ce cas, les deux spécialisations sont définies par l’utilisateur. La génération automatique d’une implémentation adjointe étant sujette à une plus grande limitation, la spécialisation adjointe contrôlée génère par défaut la spécialisation contrôlée de l’implémentation définie explicitement de la spécialisation adjointe.
Dans le cas de l'implémentation SWAP
, la meilleure option consiste à adjoindre la spécialisation contrôlée afin d’éviter de conditionner inutilement l’exécution du premier et du dernier CNOT
sur l’état des qubits de contrôle.
L’ajout d’une déclaration explicite pour la version définie par la version adjointe qui spécifie une directive de génération appropriée force le compilateur à générer la spécialisation adjointe contrôlée en fonction de l’implémentation spécifiée manuellement de la version contrôlée. Une telle déclaration explicite de spécialisation qui doit être générée par le compilateur prend la forme
controlled adjoint invert;
et est insérée à l’intérieur de la déclaration de SWAP
.
En revanche, l’insertion de la ligne
controlled adjoint distribute;
force le compilateur à générer la spécialisation en fonction de la spécialisation adjointe définie (ou générée). Pour plus d’informations, consultez cette proposition d'inférence de spécialisation partielle.
Pour l’opération SWAP
, il existe une meilleure option.
SWAP
est auto-adjointe, autrement dit, elle est son propre inverse ; l’implémentation définie par la matrice adjointe appelle simplement le corps de SWAP
. Vous l’exprimez avec la directive
adjoint self;
Déclarer ainsi la spécialisation adjointe permet de s’assurer que la spécialisation adjointe contrôlée automatiquement insérée par le compilateur appelle simplement la spécialisation contrôlée.
Les directives de génération suivantes existent et sont valides :
Spécialisation | Directive(s) |
---|---|
Spécialisation body : |
- |
Spécialisation adjoint : |
self , invert |
Spécialisation controlled : |
distribute |
Spécialisation controlled adjoint : |
self , invert , distribute |
Le fait que toutes les directives de génération soient valides pour une spécialisation adjointe contrôlée n'est pas une coïncidence. Tant que les foncteurs commutent, l'ensemble des directives de génération valides à des fins d’implémentation de la spécialisation pour une combinaison de foncteurs est toujours l'union de l'ensemble des générateurs valides pour chacun d'entre eux.
En plus des directives précédemment répertoriées, la directive auto
est valide pour toutes les spécialisations, à l’exception body
de ; elle indique que le compilateur doit choisir automatiquement une directive de génération appropriée.
La déclaration
operation DoNothing() : Unit {
body ... { }
adjoint auto;
controlled auto;
controlled adjoint auto;
}
équivaut à :
operation DoNothing() : Unit
is Adj + Ctl { }
Dans cet exemple, l’annotation is Adj + Ctl
spécifie les caractéristiques de l’opération, qui contiennent les informations relatives aux foncteurs pris en charge par une opération particulière.
Bien que, par souci de lisibilité, il soit recommandé d'annoter chaque opération avec une description complète de ses caractéristiques, le compilateur insère ou complète automatiquement l'annotation en fonction des spécialisations explicitement déclarées. À l'inverse, le compilateur génère également des spécialisations qui n’ont pas été déclarées explicitement mais qui doivent exister en fonction des caractéristiques annotées. On dit que l’annotation donnée a implicitement déclaré ces spécialisations. Le compilateur génère automatiquement les spécialisations nécessaires, le cas échéant, en choisissant une directive appropriée. Q# prend ainsi en charge l’inférence des caractéristiques de l’opération et des spécialisations existantes en fonction des annotations (partielles) et des spécialisations définies explicitement.
Dans un sens, les spécialisations sont similaires aux surcharges individuelles pour le même callable, mais certaines restrictions s’appliquent aux surcharges que vous pouvez déclarer.