Ansätze et formes variationnelles
Au cœur de tous les algorithmes variationnels se trouve l'idée clé d'analyser les différences entre des états, liés de manière commode par une application bien définie (par exemple, continue et différentiable) à partir d'un ensemble de paramètres ou de variables — d'où le nom.
Nous allons d'abord explorer comment construire des circuits paramétrés à la main. Ces circuits serviront à définir une forme variationnelle représentant une collection d'états paramétrés que notre algorithme variationnel explorera. Nous construirons ensuite notre ansatz en appliquant cette forme variationnelle à notre état de référence.
Nous verrons également comment trouver un équilibre entre vitesse et précision lors de l'exploration de cet espace de recherche.
Circuits quantiques paramétrés
Les algorithmes variationnels fonctionnent en explorant et en comparant une gamme d'états quantiques , qui dépendent d'un ensemble fini de paramètres . Ces états peuvent être préparés à l'aide d'un circuit quantique paramétré, dans lequel les portes sont définies avec des paramètres ajustables. Il est possible de créer ce circuit paramétré sans lier d'angles spécifiques au préalable :
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit rustworkx
from qiskit.circuit import QuantumCircuit, Parameter
theta = Parameter("θ")
qc = QuantumCircuit(3)
qc.rx(theta, 0)
qc.cx(0, 1)
qc.x(2)
qc.draw("mpl")
from math import pi
angle_list = [pi / 3, pi / 2]
circuits = [qc.assign_parameters({theta: angle}) for angle in angle_list]
for circuit in circuits:
display(circuit.draw("mpl"))
Forme variationnelle et ansatz
Pour optimiser itérativement depuis un état de référence vers un état cible , nous devons définir une forme variationnelle représentant une collection d'états paramétrés que notre algorithme variationnel explorera :
Remarque : l'état paramétré dépend à la fois de l'état de référence , qui ne dépend d'aucun paramètre, et de la forme variationnelle , qui dépend toujours de paramètres. Nous appelons la combinaison de ces deux moitiés un ansatz : .
Lorsque nous construisons notre ansatz pour représenter une collection d'états paramétrés à explorer, nous rencontrons un problème important : la dimensionnalité. Un système à qubits (c'est-à-dire l'espace de Hilbert) contient un nombre considérable d'états quantiques distincts dans l'espace de configuration. Il faudrait un nombre ingérable de paramètres pour l'explorer entièrement. Quantitativement, sa dimensionnalité est . Pour aggraver les choses, la complexité d'exécution des algorithmes de recherche croît exponentiellement avec cette dimensionnalité, un phénomène souvent désigné dans la littérature sous le nom de malédiction de la dimensionnalité.
Pour contrer cet obstacle, il est courant d'imposer des contraintes raisonnables sur la forme variationnelle afin que seuls les états les plus pertinents soient explorés. Trouver un ansatz tronqué efficace est un domaine de recherche actif, mais nous allons présenter deux conceptions courantes.
Ansätze heuristiques et compromis
Si tu ne disposes d'aucune information sur ton problème particulier permettant de réduire la dimensionnalité, tu peux essayer une famille arbitraire de circuits paramétrés avec moins de paramètres. Il y a cependant des compromis à considérer :
- Vitesse : En réduisant l'espace de recherche, l'algorithme peut s'exécuter plus rapidement.
- Précision : Réduire l'espace pourrait exclure la solution réelle au problème, menant à des solutions sous-optimales.
- Bruit : Les circuits plus profonds sont affectés par le bruit, il faut donc expérimenter avec la connectivité, les portes et la fidélité des portes de l'ansatz.
Il existe un compromis fondamental entre qualité (ou même résolvabilité) et vitesse : plus il y a de paramètres, plus on a de chances de trouver un résultat précis, mais plus l'algorithme prendra du temps.
Circuits N-locaux
L'un des exemples d'ansätze heuristiques les plus utilisés est celui des circuits N-locaux, pour plusieurs raisons :
- Implémentation efficace : L'ansatz N-local est généralement composé de portes locales simples qui peuvent être implémentées efficacement sur un ordinateur quantique, en utilisant un petit nombre de qubits physiques. Cela facilite la construction et l'optimisation des circuits quantiques.
- Capture des corrélations importantes : L'ansatz N-local peut capturer des corrélations importantes entre les qubits d'un système quantique, même avec un petit nombre de portes. En effet, les portes locales peuvent agir sur des qubits voisins et créer de l'intrication entre eux, ce qui peut être crucial pour simuler des systèmes quantiques complexes.
Ces circuits se composent de couches de rotation et d'intrication répétées alternativement une ou plusieurs fois comme suit :
- Chaque couche est formée de portes de taille au plus , où doit être inférieur au nombre de qubits.
- Pour une couche de rotation, les portes sont empilées les unes sur les autres. On peut utiliser des opérations de rotation standard, comme
RXouCRZ. - Pour une couche d'intrication, on peut utiliser des portes comme les portes
ToffoliouCXavec une stratégie d'intrication. - Les deux types de couches peuvent être paramétrés ou non, mais au moins l'un d'eux doit contenir des paramètres. Sinon, sans au moins un paramètre, il n'y aurait pas de variations !
- Optionnellement, une couche de rotation supplémentaire est ajoutée à la fin du circuit.
Par exemple, créons un circuit NLocal à cinq qubits avec des blocs de rotation formés par des portes RX et CRZ, des blocs d'intrication formés par des portes Toffoli agissant sur les qubits , , et , avec répétitions de chaque couche.
from qiskit.circuit.library import NLocal, CCXGate, CRZGate, RXGate
from qiskit.circuit import Parameter
theta = Parameter("θ")
ansatz = NLocal(
num_qubits=5,
rotation_blocks=[RXGate(theta), CRZGate(theta)],
entanglement_blocks=CCXGate(),
entanglement=[[0, 1, 2], [0, 2, 3], [4, 2, 1], [3, 1, 0]],
reps=2,
insert_barriers=True,
)
ansatz.decompose().draw("mpl")
Dans l'exemple ci-dessus, la plus grande porte est la porte Toffoli, qui agit sur trois qubits, rendant le circuit -local. Le type le plus couramment utilisé de circuits -locaux est celui des circuits -locaux avec des portes de rotation à un qubit et des portes d'intrication à qubits.
Créons un circuit -local à l'aide de la classe TwoLocal de Qiskit. La syntaxe est la même que pour NLocal, mais il y a quelques différences. Par exemple, la plupart des portes, comme RX, RZ et CNOT, peuvent être passées sous forme de chaînes sans importer les portes ni créer une instance Parameter.
from qiskit.circuit.library import TwoLocal
ansatz = TwoLocal(
num_qubits=5,
rotation_blocks=["rx", "rz"],
entanglement_blocks="cx",
entanglement="linear",
reps=2,
insert_barriers=True,
)
ansatz.decompose().draw("mpl")
Dans ce cas, nous avons utilisé la distribution d'intrication linéaire, où chaque qubit est intriqué avec le suivant. Pour en savoir plus sur les autres stratégies, consulte la documentation de TwoLocal.
Efficient SU2
efficient_su2 est un circuit efficace pour le matériel qui se compose de couches d'opérations à un qubit couvrant SU(2) et d'intrications CX. Il s'agit d'un motif heuristique pouvant être utilisé pour préparer des fonctions d'onde d'essai pour les algorithmes quantiques variationnels ou comme circuit de classification pour l'apprentissage automatique.
from qiskit.circuit.library import efficient_su2
ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
ansatz.decompose().draw("mpl")
Ansätze spécifiques au problème
Tandis que les ansätze heuristiques et efficaces pour le matériel nous aident à résoudre un problème de manière naïve, nous pouvons utiliser des connaissances spécifiques au problème pour restreindre notre espace de recherche de circuits à un type spécifique. Cela nous permettra de gagner en vitesse sans perdre en précision dans notre processus de recherche.
Optimisation
Dans un problème de coupe maximale (max-cut), nous voulons partitionner les nœuds d'un graphe de manière à maximiser le nombre d'arêtes entre les nœuds de groupes différents. La partition optimale pour le graphe ci-dessous est claire : le nœud 0 à gauche doit être séparé des autres nœuds à droite par une coupe.
import rustworkx as rx
from rustworkx.visualization import mpl_draw
n = 4
G = rx.PyGraph()
G.add_nodes_from(range(n))
# The edge syntax is (start, end, weight)
edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]
G.add_edges_from(edges)
mpl_draw(
G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color="#1192E8"
)
Pour utiliser l'algorithme QAOA pour un problème de max-cut, nous avons besoin d'un Hamiltonien de Pauli qui encode le coût de telle sorte que la valeur d'espérance minimale de l'opérateur corresponde au nombre maximal d'arêtes entre les nœuds de deux groupes différents.
Pour cet exemple simple, l'opérateur est une combinaison linéaire de termes avec des opérateurs Z sur les nœuds reliés par une arête (rappel : le qubit 0 est le plus à droite) : . Une fois l'opérateur construit, l'ansatz pour l'algorithme QAOA peut facilement être créé en utilisant le circuit QAOAAnsatz de la bibliothèque de circuits Qiskit.
# Pre-defined ansatz circuit, operator class and visualization tools
from qiskit.circuit.library import QAOAAnsatz
from qiskit.quantum_info import SparsePauliOp
# Problem to Hamiltonian operator
hamiltonian = SparsePauliOp.from_list(
[("ZZII", 1), ("IZZI", 1), ("ZIIZ", 1), ("IZIZ", 1), ("IIZZ", 1)]
)
# QAOA ansatz circuit
ansatz = QAOAAnsatz(hamiltonian, reps=2)
# Draw
ansatz.decompose(reps=3).draw("mpl")
L'image précédente illustre l'ansatz en portes de base pour plus de clarté. Cependant, il peut être exprimé à plusieurs niveaux de décomposition en modifiant l'argument reps ou en dessinant le circuit sans la méthode de décomposition. Par exemple, la représentation suivante montre directement la structure QAOA avec la valeur reps par défaut, soit reps=1.
ansatz.decompose(reps=2).draw("mpl")
Apprentissage automatique quantique
En apprentissage automatique, une application courante est la classification des données en deux catégories ou plus. Cela implique d'encoder un point de données dans une carte de caractéristiques qui fait correspondre des vecteurs de caractéristiques classiques à l'espace de Hilbert quantique. La construction de cartes de caractéristiques quantiques basées sur des circuits quantiques paramétrés, difficiles à simuler classiquement, est une étape importante vers l'obtention d'un avantage potentiel sur les approches d'apprentissage automatique classiques et constitue un domaine de recherche actif.
La zz_feature_map peut être utilisée pour créer un circuit paramétré. Nous pouvons passer nos points de données à la carte de caractéristiques () et une forme variationnelle séparée pour passer des poids comme paramètres ().
from qiskit.circuit.library import zz_feature_map, TwoLocal
data = [0.1, 0.2]
zz_feature_map_reference = zz_feature_map(feature_dimension=2, reps=2)
zz_feature_map_reference = zz_feature_map_reference.assign_parameters(data)
variation_form = TwoLocal(2, ["ry", "rz"], "cz", reps=2)
vqc_ansatz = zz_feature_map_reference.compose(variation_form)
vqc_ansatz.decompose().draw("mpl")
Résumé
Avec cette leçon, tu as appris à définir ton espace de recherche avec une forme variationnelle :
- Préparer des états avec un circuit quantique paramétré, où les portes sont définies avec des paramètres ajustables
- Comment construire des ansätze qui font le compromis entre vitesse et précision
- Ansätze heuristiques
- Ansätze spécifiques au problème
Notre charge de travail variationnelle de haut niveau ressemble à ceci :
Pour chaque paramètre variationnel , un état quantique différent sera produit. Pour trouver les paramètres optimaux, nous devons définir une fonction de coût spécifique au problème pour mettre à jour itérativement les paramètres de notre ansatz.