Aller au contenu principal

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.

Un diagramme montrant les composants clés de la discussion sur l'ansatz, incluant les ansätze heuristiques et les ansätze spécifiques au problème.

Circuits quantiques paramétrés

Les algorithmes variationnels fonctionnent en explorant et en comparant une gamme d'états quantiques ψ(θ)|\psi(\vec{\theta})\rangle, qui dépendent d'un ensemble fini de kk paramètres θ=(θ0,,θk1)\vec{\theta} = (\theta^0, \ldots, \theta^{k-1}). 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")

Output of the previous code cell

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"))

Output of the previous code cell

Output of the previous code cell

Forme variationnelle et ansatz

Pour optimiser itérativement depuis un état de référence ρ|\rho\rangle vers un état cible ψ(θ)|\psi(\vec\theta)\rangle, nous devons définir une forme variationnelle UV(θ)U_V(\vec{\theta}) représentant une collection d'états paramétrés que notre algorithme variationnel explorera :

0URUR0=ρUV(θ)UA(θ)0=UV(θ)UR0=UV(θ)ρ=ψ(θ)\begin{aligned} |0\rangle \xrightarrow{U_R} U_R|0\rangle & = |\rho\rangle \xrightarrow{U_V(\vec{\theta})} U_A(\vec{\theta})|0\rangle \\[1mm] & = U_V(\vec{\theta})U_R|0\rangle \\[1mm] & = U_V(\vec{\theta})|\rho\rangle \\[1mm] & = |\psi(\vec{\theta})\rangle \\[1mm] \end{aligned}

Remarque : l'état paramétré dépend à la fois de l'état de référence ρ|\rho\rangle, qui ne dépend d'aucun paramètre, et de la forme variationnelle UV(θ)U_V(\vec{\theta}), qui dépend toujours de paramètres. Nous appelons la combinaison de ces deux moitiés un ansatz : UA(θ):=UV(θ)URU_A(\vec\theta) := U_V(\vec\theta)U_R.

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 à nn 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 D=22nD = 2^{2n}. 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 22n2^{2n} 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 NN, où NN 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 RX ou CRZ.
  • Pour une couche d'intrication, on peut utiliser des portes comme les portes Toffoli ou CX avec 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 [0,1,2][0,1,2], [0,2,3][0,2,3], [4,2,1][4,2,1] et [3,1,0][3,1,0], avec 22 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")

Output of the previous code cell

Dans l'exemple ci-dessus, la plus grande porte est la porte Toffoli, qui agit sur trois qubits, rendant le circuit 33-local. Le type le plus couramment utilisé de circuits NN-locaux est celui des circuits 22-locaux avec des portes de rotation à un qubit et des portes d'intrication à 22 qubits.

Créons un circuit 22-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")

Output of the previous code cell

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")

Output of the previous code cell

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"
)

Output of the previous code cell

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) : ZZII+IZZI+ZIIZ+IZIZ+IIZZZZII + IZZI + ZIIZ + IZIZ + IIZZ. 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")

Output of the previous code cell

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")

Output of the previous code cell

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 (xx) et une forme variationnelle séparée pour passer des poids comme paramètres (θ\theta).

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")

Output of the previous code cell

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 :

Un diagramme de circuit montrant deux unitaires : l'un préparant l'état de référence et l'autre préparant l'ansatz.

Pour chaque paramètre variationnel θ\vec\theta, 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.