Avancé Chapitre 24-25 / 16

VQE (Variational Quantum Eigensolver) & QAOA (Quantum Approximate Optimization)

Comprendre les deux piliers des algorithmes hybrides quantique-classique — VQE pour l'énergie fondamentale, QAOA pour l'optimisation combinatoire — sans prérequis mathématiques, avec du code Q# et Qiskit.

VQE — Variational Quantum Eigensolver

L’idée en une phrase

VQE est un algorithme hybride : le processeur quantique évalue l’énergie d’un état paramétré, et un optimiseur classique ajuste les paramètres pour minimiser cette énergie. Pensez à un thermostat intelligent : le capteur (circuit quantique) mesure la température (énergie), et le régulateur (ordinateur classique) tourne le bouton (paramètres θ) jusqu’à trouver le minimum.

Concepts clés

  • Principe variationnel : pour tout état |ψ(θ)⟩, l’énergie mesurée ⟨ψ(θ)|H|ψ(θ)⟩ est toujours ≥ l’énergie fondamentale E₀ du hamiltonien H. En minimisant sur θ, on approche E₀ par le dessus.
  • Ansatz : c’est le « gabarit » du circuit paramétré — une suite de portes avec des angles θ₁, θ₂, … que l’optimiseur va ajuster. Comme un patron de couture : la forme est fixée, mais les mesures (angles) changent.
  • Fonction de coût : C(θ) = ⟨ψ(θ)|H|ψ(θ)⟩. On la calcule en décomposant H en somme de termes de Pauli (ex : 0.5·ZZ + 0.3·XI − 0.2·IZ) et en mesurant chaque terme séparément sur le circuit. La somme pondérée donne C(θ).
  • Boucle hybride : 1) Préparer |ψ(θ)⟩ sur le QPU → 2) Mesurer ⟨H⟩ → 3) Envoyer la valeur à l’optimiseur classique (COBYLA, SPSA, L-BFGS-B…) → 4) Recevoir de nouveaux θ → retour à 1). On itère jusqu’à convergence.

Ansatz : comment choisir ?

  • Hardware-efficient ansatz (HEA) : couches de rotations Ry/Rz + CNOT en échelle. Facile à implémenter sur n’importe quel matériel, mais peut souffrir de barren plateaus (paysage plat où l’optimiseur ne trouve pas de direction).
  • UCCSD (Unitary Coupled-Cluster) : inspiré de la chimie quantique, plus expressif mais circuits très profonds — gourmand en portes.
  • Ansatz problem-aware : conçu pour respecter les symétries du problème (ex : conservation du nombre de particules). Meilleur compromis en pratique.

Tableau récapitulatif

ConceptDescriptionAnalogie
Hamiltonien HOpérateur qui encode l’énergie du systèmeLa « fonction objectif » à minimiser
AnsatzCircuit paramétré |ψ(θ)⟩Un patron de couture ajustable
Valeur propre minimale E₀Énergie fondamentale recherchéeLe fond de la vallée dans un paysage
Optimiseur classiqueCOBYLA, SPSA, L-BFGS-B…Le GPS qui guide vers la vallée
Décomposition de PauliH = Σᵢ cᵢ PᵢDécouper un calcul complexe en mesures simples

Code Qiskit — VQE minimal

# VQE pour trouver l'énergie fondamentale de H = 0.5*ZZ + 0.3*XI - 0.2*IZ
from qiskit.circuit.library import EfficientSU2
from qiskit_algorithms import VQE
from qiskit_algorithms.optimizers import COBYLA
from qiskit.primitives import Estimator
from qiskit.quantum_info import SparsePauliOp

# Étape 1 : Définir le hamiltonien comme somme de termes de Pauli
hamiltonian = SparsePauliOp.from_list([
    ("ZZ", 0.5), ("XI", 0.3), ("IZ", -0.2)
])

# Étape 2 : Choisir l'ansatz (circuit paramétré)
ansatz = EfficientSU2(num_qubits=2, reps=1)
# reps=1 → une seule couche de rotations + intrication
# Plus de couches = plus expressif, mais plus de paramètres

# Étape 3 : Lancer VQE avec un optimiseur classique
estimator = Estimator()
optimizer = COBYLA(maxiter=200)

vqe = VQE(estimator, ansatz, optimizer)
result = vqe.compute_minimum_eigenvalue(hamiltonian)

print(f"Énergie fondamentale estimée : {result.eigenvalue:.4f}")
print(f"Paramètres optimaux : {result.optimal_parameters}")

Code Q# — Évaluation d’un terme de Pauli

// Mesurer la valeur attendue de Z⊗Z sur un état paramétré
operation MeasureZZ(theta1 : Double, theta2 : Double) : Double {
    mutable totalParity = 0;
    let nShots = 1000;

    for _ in 1..nShots {
        use (q0, q1) = (Qubit(), Qubit());

        // Ansatz simple : rotations Ry paramétrées
        Ry(theta1, q0);
        Ry(theta2, q1);
        CNOT(q0, q1);

        // Mesure dans la base Z pour les deux qubits
        let r0 = M(q0) == One ? -1 | 1;
        let r1 = M(q1) == One ? -1 | 1;

        // ZZ → produit des résultats (+1 ou -1)
        set totalParity += r0 * r1;

        ResetAll([q0, q1]);
    }
    // Valeur attendue ≈ moyenne des parités
    return IntAsDouble(totalParity) / IntAsDouble(nShots);
}

⚠ Piège fréquent : VQE ne garantit pas de trouver le minimum global — l’optimiseur classique peut rester coincé dans un minimum local. De plus, avec un ansatz trop expressif (trop de paramètres par rapport au problème), on tombe dans le phénomène de barren plateaus : les gradients deviennent exponentiellement petits et l’optimiseur tourne en rond.


QAOA — Quantum Approximate Optimization Algorithm

L’idée en une phrase

QAOA est un algorithme hybride conçu pour les problèmes d’optimisation combinatoire (ex : MaxCut, voyageur de commerce). Il alterne deux types de « couches » quantiques — une qui encode le problème, une qui explore les solutions — puis un optimiseur classique règle les paramètres pour maximiser la qualité de la solution trouvée.

Concepts clés

  • Structure du circuit : on part de l’état |+⟩⊗ⁿ (superposition uniforme), puis on applique p couches alternées : e^{-iγₖHc} (opérateur coût) suivi de e^{-iβₖHm} (opérateur mélangeur). Les 2p paramètres (γ₁, β₁, …, γₚ, βₚ) sont optimisés classiquement.
  • Hc (hamiltonien coût) : encode la fonction objectif du problème. Pour MaxCut : Hc = Σ (1 − ZᵢZⱼ)/2 sur chaque arête (i,j). Chaque terme vaut 1 si les qubits i et j ont des valeurs différentes (arête coupée), 0 sinon.
  • Hm (hamiltonien mélangeur) : typiquement Hm = Σ Xᵢ (somme de portes X sur chaque qubit). Son rôle : brasser les solutions candidates pour explorer l’espace, comme un shaker qui mélange les ingrédients.
  • Profondeur p : plus p est grand, plus le circuit est expressif — et plus l’approximation est bonne. Mais plus le circuit est profond, plus il est sensible au bruit. En pratique sur du matériel NISQ, p = 1 à 5 est typique.

MaxCut — le problème emblématique

Soit un graphe avec des nœuds et des arêtes. On veut colorer chaque nœud en noir ou blanc pour maximiser le nombre d’arêtes reliant un nœud noir à un nœud blanc (« couper » un maximum d’arêtes). C’est NP-difficile en général, mais QAOA donne de bonnes approximations.

Chaque qubit représente un nœud : |0⟩ = noir, |1⟩ = blanc. L’opérateur coût applique des portes ZZ paramétrées sur chaque arête : CNOT(i,j) → Rz(γ,j) → CNOT(i,j). Le mélangeur applique Rx(2β) sur chaque qubit.

Tableau récapitulatif — VQE vs QAOA

AspectVQEQAOA
Objectif principalTrouver l’énergie fondamentaleOptimisation combinatoire
AnsatzLibre (HEA, UCCSD…)Structuré : (Hc, Hm) alternés
ParamètresVariable (dépend de l’ansatz)2p (γ₁, β₁ … γₚ, βₚ)
Domaine favoriChimie quantique, physiqueGraphes, planification, logistique
Garantie théoriqueBorne sup sur E₀Ratio d’approximation (p → ∞ = exact)
Comparaison classiqueMéthodes CI, DFTRecuit simulé, algorithmes gloutons

Code Qiskit — QAOA pour MaxCut

# QAOA pour MaxCut sur un triangle (3 nœuds, 3 arêtes)
from qiskit_algorithms import QAOA
from qiskit_algorithms.optimizers import COBYLA
from qiskit.primitives import Sampler
from qiskit.quantum_info import SparsePauliOp

# Étape 1 : Définir le hamiltonien coût pour MaxCut
# Graphe triangle : arêtes (0,1), (1,2), (0,2)
# Hc = Σ (1 - ZiZj)/2 = constante - 0.5*(Z0Z1 + Z1Z2 + Z0Z2)
cost_op = SparsePauliOp.from_list([
    ("ZZI", -0.5),  # arête 0-1
    ("IZZ", -0.5),  # arête 1-2
    ("ZIZ", -0.5),  # arête 0-2
])

# Étape 2 : Configurer QAOA avec p=2 couches
sampler = Sampler()
optimizer = COBYLA(maxiter=150)
qaoa = QAOA(sampler, optimizer, reps=2)  # reps = p

# Étape 3 : Résoudre
result = qaoa.compute_minimum_eigenvalue(cost_op)

# Le bitstring le plus probable = meilleure coupe
print(f"Meilleur bitstring : {result.best_measurement['bitstring']}")
print(f"Valeur du coût : {result.best_measurement['value']:.4f}")

Code Q# — Une couche QAOA

// Une couche QAOA : opérateur coût (ZZ sur chaque arête) + mélangeur (Rx)
operation QAOALayer(
    qs : Qubit[],
    edges : (Int, Int)[],
    gamma : Double,
    beta : Double
) : Unit is Adj + Ctl {
    // Opérateur coût : e^{-i γ ZiZj} pour chaque arête
    for (i, j) in edges {
        CNOT(qs[i], qs[j]);
        Rz(2.0 * gamma, qs[j]);  // Facteur 2 car Rz(θ) = e^{-iθZ/2}
        CNOT(qs[i], qs[j]);
    }

    // Opérateur mélangeur : e^{-i β Xi} = Rx(2β) sur chaque qubit
    for q in qs {
        Rx(2.0 * beta, q);
    }
}

// Circuit QAOA complet pour p couches
operation RunQAOA(
    n : Int,
    edges : (Int, Int)[],
    gammas : Double[],
    betas : Double[],
    p : Int
) : Result[] {
    use qs = Qubit[n];

    // État initial : superposition uniforme |+⟩^n
    for q in qs { H(q); }

    // Appliquer p couches alternées
    for k in 0..p-1 {
        QAOALayer(qs, edges, gammas[k], betas[k]);
    }

    // Mesurer tous les qubits
    return MeasureEachZ(qs);
}

⚠ Piège fréquent : QAOA avec p=1 ne garantit qu’un ratio d’approximation limité (par exemple ≈ 0.6924 pour MaxCut sur des graphes 3-réguliers). Pour approcher la solution exacte, il faut augmenter p — mais chaque couche supplémentaire allonge le circuit et amplifie le bruit. C’est le compromis central du NISQ : expressivité vs fidélité.


Quiz — teste tes connaissances
Avancé 7 questions Objectif : 5/7 minimum
0/7
bonnes reponses
Objectif non atteint (minimum 5/7 requis).
Remonte relire la fiche memo ci-dessus en pretant attention aux points rates, puis clique sur « Recommencer » pour retenter.