Aller au contenu principal

L'ansatz

Regarde Victoria Lipinska expliquer ce qu'est un ansatz et pourquoi il est important dans le contexte d'un solveur propre quantique variationnel (VQE).

Références

Les articles suivants sont cités dans la vidéo ci-dessus.

Code de l'ansatz

Dans la leçon précédente, tu as créé un Hamiltonien qui décrit l'énergie de la molécule qui t'intéresse, et tu l'as mis sous un format exploitable par un ordinateur quantique. Le VQE utilise un circuit variationnel pour préparer des états quantiques. On se sert ensuite de ces états pour déterminer la valeur attendue de l'Hamiltonien (l'énergie). Les paramètres du circuit variationnel sont ajustés jusqu'à ce que le calcul converge vers une valeur attendue minimale. Dans le contexte de la chimie quantique, cette valeur doit correspondre à l'énergie de l'état fondamental. Cette leçon se concentre sur le circuit variationnel, également appelé ansatz (un mot allemand signifiant « approche » ou « méthode »). Dans cette leçon, tu apprendras :

  • l'ensemble des ansaetze prêts à l'emploi disponibles dans la bibliothèque de circuits
  • comment spécifier ou modifier les caractéristiques d'un ansatz
  • comment construire ton propre ansatz
  • des exemples de bons et de mauvais ansaetze

La bibliothèque de circuits Qiskit propose de nombreuses catégories de circuits pouvant servir d'ansatz. Ici, nous limiterons notre discussion aux circuits deux-locaux (circuits composés de portes agissant sur au plus deux qubits à la fois). Efficient SU2 est un ansatz couramment utilisé.

Un circuit efficient_su_2 est constitué de couches d'opérations sur un seul qubit couvertes par SU(2) (groupe unitaire spécial de degré 2, comme les portes de rotation de Pauli) et d'enchevêtrements CX. Il s'agit d'un schéma heuristique qui peut être utile dans des algorithmes quantiques variationnels comme le VQE et les circuits de classification en apprentissage automatique quantique (QML).

Commençons par un exemple de circuit efficient_su2 à quatre qubits avec deux types de portes SU(2), disons rx et y. On spécifie également un schéma d'enchevêtrement et le nombre de répétitions. Si tu te contentes de .draw() les circuits, tu obtiendras une représentation assez abstraite. On obtient un schéma de circuit plus lisible avec .decompose().draw(), et ici on utilisera output = "mpl".

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit
from qiskit.circuit.library import efficient_su2

SU2_ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
print(SU2_ansatz.draw())
SU2_ansatz.decompose().draw(output="mpl")
┌──────────┐┌───┐     ┌──────────┐   ┌───┐
q_0: ┤ Rx(θ[0]) ├┤ Y ├──■──┤ Rx(θ[4]) ├───┤ Y ├─────────────────────
├──────────┤├───┤┌─┴─┐└──────────┘┌──┴───┴───┐ ┌───┐
q_1: ┤ Rx(θ[1]) ├┤ Y ├┤ X ├─────■──────┤ Rx(θ[5]) ├───┤ Y ├─────────
├──────────┤├───┤└───┘ ┌─┴─┐ └──────────┘┌──┴───┴───┐┌───┐
q_2: ┤ Rx(θ[2]) ├┤ Y ├────────┤ X ├─────────■──────┤ Rx(θ[6]) ├┤ Y ├
├──────────┤├───┤ └───┘ ┌─┴─┐ ├──────────┤├───┤
q_3: ┤ Rx(θ[3]) ├┤ Y ├────────────────────┤ X ├────┤ Rx(θ[7]) ├┤ Y ├
└──────────┘└───┘ └───┘ └──────────┘└───┘

Output of the previous code cell

Les portes SU(2) apparaissent au début et à la fin, dans l'ordre et avec les éléments spécifiés dans su2_gates = [...]. Le schéma d'enchevêtrement linear signifie que les portes CX parcourent les qubits numérotés en séquence, enchevêtrant 0 & 1, puis 1 & 2, et ainsi de suite en diagonale dans le circuit. Comme tu peux t'y attendre, définir reps = 2 ajoute simplement une couche d'enchevêtrement et une couche SU(2) finale. Définir reps = n correspond à n couches d'enchevêtrement, avec des couches SU(2) entre elles et à chaque extrémité.

SU2_ansatz2 = efficient_su2(
4, su2_gates=["rx", "y", "z"], entanglement="linear", reps=2
)
SU2_ansatz2.decompose().draw(output="mpl")

Output of the previous code cell

Il existe plusieurs autres schémas d'enchevêtrement. Deux méritent d'être mentionnés : circular et full. L'enchevêtrement circulaire est identique à l'enchevêtrement linéaire, mais avec une porte CX supplémentaire enchevêtrant le premier et le dernier qubit. Le schéma d'enchevêtrement complet inclut une porte CX entre chaque paire de qubits. Note que pour un circuit à N qubits, cela représente N(N1)/2N(N-1)/2 portes CXCX, ce qui peut devenir coûteux en termes de calcul.

SU2_ansatz3 = efficient_su2(
4, su2_gates=["rx", "y", "z"], entanglement="circular", reps=1
)
SU2_ansatz3.decompose().draw(output="mpl")

Output of the previous code cell

SU2_ansatz4 = efficient_su2(4, su2_gates=["rx", "y", "z"], entanglement="full", reps=1)
SU2_ansatz4.decompose().draw(output="mpl")

Output of the previous code cell

Tu peux surveiller la profondeur de tes circuits en utilisant .depth(), ou parfois .decompose().depth().

print(SU2_ansatz4.decompose().depth())
11

Une généralisation de l'efficient_su2 est le circuit deux-local, qui est lui-même un cas particulier des circuits n-locaux. Les circuits deux-locaux contiennent également des blocs SU(2) (ou blocs de rotation) et des blocs d'enchevêtrement. Ici, on est libre de spécifier le type de portes d'enchevêtrement à utiliser, par exemple des portes CRX. Dans cet exemple, toutes les portes acceptent un paramètre, mais ce n'est pas forcément le cas. On pourrait par exemple utiliser des portes de rotation Y et des portes d'enchevêtrement CX.

from qiskit.circuit.library import n_local

rotation_blocks = ["ry"]
entanglement_blocks = ["crx"]
two_ansatz = n_local(
4, rotation_blocks, entanglement_blocks, "linear", insert_barriers=True, reps=2
)
two_ansatz.decompose().draw(output="mpl")

Output of the previous code cell

Le dernier ansatz que nous aborderons par son nom est le Pauli-two-design. Ce circuit contient une rotation initiale de RY(pi/4)RY(pi/4), et les couches de rotation contiennent des rotations de Pauli sur un seul qubit, où l'axe est choisi uniformément au hasard parmi X, Y ou Z. Les couches d'enchevêtrement sont composées de portes CZ par paires avec une profondeur totale de deux. Remarque la différence de profondeur d'enchevêtrement (et de circuit total) entre ce pauli_two_design et, par exemple, l'efficient_su2.

from qiskit.circuit.library import pauli_two_design

PtwoD_ansatz = pauli_two_design(5, reps=1, seed=10599, insert_barriers=True)
PtwoD_ansatz.decompose().draw(output="mpl")

Output of the previous code cell

Ces circuits variationnels prêts à l'emploi sont des heuristiques utiles, tant pour atteindre un niveau d'enchevêtrement souhaité que pour limiter la profondeur du circuit. Mais il n'y a rien de magique en eux. Tu es libre de créer ton propre circuit variationnel. C'est même potentiellement avantageux dans les cas où tu connais quelque chose sur l'enchevêtrement de l'état cible de ton système.

Pour créer ton propre ansatz, il te suffit de construire un circuit quantique dans lequel un sous-ensemble de portes est une fonction d'éléments d'un vecteur de paramètres (« theta » dans l'exemple à trois qubits ci-dessous).

from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector

n = 3

theta = ParameterVector("θ", length=n)
qc = QuantumCircuit(n)
qc.h(0)
qc.h(2)
for i in range(n - 1):
qc.cx(i, i + 1)
qc.cz(0, n - 1)
qc.barrier()
for i in range(n):
qc.ry(theta[i], i)
qc.barrier()
qc.cz(0, n - 1)
for i in reversed(range(n - 1)):
qc.cx(i, i + 1)
qc.h(0)
qc.h(1)
own_ansatz = qc
print(own_ansatz.depth())
qc.draw("mpl")
9

Output of the previous code cell

En règle générale, choisir le meilleur ansatz est un art ; le meilleur ansatz est celui qui t'aide à atteindre ta cible en le moins d'étapes d'optimisation possible. Il est plus facile d'identifier les ansaetze susceptibles d'être mauvais. Par exemple, une profondeur de circuit plus grande tend à entraîner une accumulation d'erreurs. La mitigation des erreurs peut aider, mais c'est une bonne pratique de maintenir la profondeur du circuit aussi faible que raisonnable. N'omets cependant pas l'enchevêtrement nécessaire. Tu pourrais avoir un état cible qui requiert un schéma d'enchevêtrement complet. Deux exemples ci-dessous sont susceptibles d'être de mauvais choix pour des raisons assez évidentes. Le choix d'un bon ansatz sera revisité dans les sections ultérieures dans le contexte des tests de convergence.

Ce premier circuit est probablement un mauvais choix car le dernier qubit n'est pas du tout enchevêtré avec les autres. En fait, aucune action computationnellement significative n'est effectuée sur le dernier qubit. Selon toute vraisemblance, ce dernier qubit devrait soit être enchevêtré avec les autres, soit être retiré du calcul.

n = 4

theta = ParameterVector("θ", length=n)
qc = QuantumCircuit(n)
qc.h(0)
qc.h(2)
for i in range(n - 2):
qc.cx(i, i + 1)
qc.cz(0, n - 2)
qc.barrier()
for i in range(n):
qc.ry(theta[i], i)
qc.barrier()
qc.cz(0, n - 2)
for i in reversed(range(n - 2)):
qc.cx(i, i + 1)
qc.h(0)
qc.h(1)
own_ansatz2 = qc
print(own_ansatz2.depth())
qc.draw("mpl")
9

Output of the previous code cell

Ce dernier circuit est probablement un mauvais choix, car la profondeur de portes est très élevée, et répéter la couche d'enchevêtrement quatre fois ne donnera vraisemblablement pas un résultat sensiblement meilleur que deux ou trois répétitions.

su2_ansatz_long = efficient_su2(
4, su2_gates=["rx", "y", "z"], entanglement="linear", reps=4
)
print(su2_ansatz_long.decompose().depth())
su2_ansatz_long.decompose().draw(output="mpl")
24

Output of the previous code cell

Ces circuits ne sont pas « complets » au sens où il reste des paramètres inconnus et variables à insérer dans bon nombre des portes. Ces paramètres sont choisis en formulant des hypothèses successives et en mettant à jour les paramètres pour réduire la valeur attendue de la fonction de coût (dans le contexte de la chimie, typiquement l'énergie de l'état fondamental). Dans une ou même quelques dimensions, c'est trivial. Mais le circuit ci-dessus comporte 20 paramètres variationnels, ce qui signifie que trouver l'état cible avec l'énergie minimale revient à parcourir un espace à 20 dimensions (une raison de plus de ne pas inclure des portes de circuit inutiles). C'est là qu'interviennent les algorithmes d'optimisation classiques, et c'est le sujet de la prochaine leçon.