Skip to main content
IBM Quantum Platform

QAOA à démarrage à chaud avec le module complémentaire « Optimization Mapper » de Qiskit

Durée d'utilisation estimée : 9 minutes sur un Heron r3 (REMARQUE : il s'agit uniquement d'une estimation. (La durée d'exécution peut varier.)


Acquis d'apprentissage

À l'issue de ce tutoriel, vous devriez être en mesure de comprendre les éléments suivants :

  • Comment transposer un problème de « max-cut » en une formulation quantique d'optimisation binaire quadratique sans contraintes (QUBO) à l'aide de qiskit-addon-opt-mapper
  • Comment implémenter et exécuter le QAOA standard sur un simulateur
  • Comment appliquer la méthode WS-QAOA en calculant la relaxation du programme quadratique (QP) et en concevant le circuit de « warm-start »
  • Comment comparer la convergence énergétique et la qualité des solutions entre le QAOA standard et le WS-QAOA

Prérequis

Nous vous recommandons de vous familiariser avec les sujets suivants :


Arrière-plan

L'algorithme d'optimisation quantique approximative (QAOA) est un algorithme hybride quantique-classique conçu pour résoudre des problèmes d'optimisation combinatoire tels que le « max-cut » et les formulations QUBO générales. Pour une introduction de base au QAOA dans Qiskit, consultez le tutoriel sur le QAOA; pour découvrir des techniques plus avancées de conception de circuits, consultez le tutoriel avancé sur le QAOA.

Dans le QAOA standard :

  • L'état initial est la superposition uniforme +n|+\rangle^{\otimes n}.
  • Les paramètres variationnels sont initialisés de manière aléatoire.
  • Un optimiseur classique recherche les paramètres qui minimisent la fonction de coût.

Cependant, pour des problèmes de taille réaliste et du matériel quantique sujet au bruit, une initialisation aléatoire peut entraîner une convergence lente, des minima locaux défavorables et un coût d'optimisation accru.

La méthode QAOA à démarrage à chaud (WS-QAOA) améliore ce résultat en intégrant directement les principes de l'optimisation classique dans le circuit quantique. Ce tutoriel suit les méthodes présentées par Egger, Mareček et Woerner dans leur article intitulé « Warm-starting quantum optimization ». L'idée principale est la suivante :

  1. Résoudre une relaxation continue du problème binaire d'origine (un programme quadratique sur [0,1]n[0,1]^n au lieu de {0,1}n\{0,1\}^n ).
  2. Encoder la solution « relaxed » ci[0,1]c^*_i \in [0,1] en un état initial personnalisé à l'aide de YY -rotation angles θi=2arcsin(ci)\theta_i = 2\arcsin(\sqrt{c^*_i}), de sorte que le qubit ii se trouve initialement dans un état dont la probabilité de mesure 1|1\rangle est de cic^*_i.
  3. Remplacer le mélangeur standard XX par un mélangeur personnalisé dont l'état de base correspond à l'état initial de « démarrage à chaud », ce qui garantit que l'algorithme démarre près de la solution classique et puisse explorer son voisinage.

Un paramètre de régularisation ε[0,0.5]\varepsilon \in [0, 0.5] limite cic^*_i en dehors de l'intervalle [0, 1] afin d'éviter les problèmes d'accessibilité; les qubits initialisés à 0|0\rangle ou 1|1\rangle ne peuvent pas être déplacés par l'hamiltonien de coût. Lorsque ε=0.5\varepsilon = 0.5, WS-QAOA se réduit exactement à la version standard de QAOA.

La modélisation du problème s'appuie sur le qiskit-addon-opt-mapper package, dont Maxcut la classe d'application construit directement le QUBO à partir d'un graphe, et dont les convertisseurs et traducteurs associent le problème obtenu à des hamiltoniens quantiques.


Exigences

Avant de commencer ce tutoriel, assurez-vous d'avoir installé les éléments suivants :

  • Qiskit SDK v2.0 ou version ultérieure, avec prise en charge de la visualisation
  • Qiskit Runtime v0.43 ou version ultérieure (pip install qiskit-ibm-runtime)
  • Module complémentaire « Optimization Mapper » pour Qiskit (pip install qiskit-addon-opt-mapper)
  • SciPy (pip install scipy)
  • NetworkX (pip install networkx)

Configuration

Importez toutes les bibliothèques nécessaires et définissez les fonctions d'aide utilisées tout au long de ce tutoriel.

import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from scipy.optimize import minimize

from qiskit.circuit import QuantumCircuit, ParameterVector
from qiskit.circuit.library import qaoa_ansatz
from qiskit.quantum_info import Statevector
from qiskit.primitives import StatevectorEstimator, StatevectorSampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    Session,
    EstimatorOptions,
    EstimatorV2 as Estimator,
    SamplerV2 as Sampler,
)

from qiskit_addon_opt_mapper.applications import Maxcut
from qiskit_addon_opt_mapper.converters import OptimizationProblemToQubo
from qiskit_addon_opt_mapper.translators import to_ising

Exemple de simulateur à petite échelle

Nous prendrons comme exemple concret un petit problème de « max-cut » sur un graphe pondéré. Max-cut pose la question suivante : étant donné un graphe G=(V,E)G=(V,E) avec des poids d'arêtes wijw_{ij}, trouver une partition des sommets en deux ensembles SS et Sˉ\bar{S} qui maximise le poids total des arêtes traversant la coupure.

En tant que problème de minimisation QUBO, le problème du « max-cut » peut s'écrire comme suit : minx{0,1}n(i,j)Ewij(xi+xj2xixj)\min_{x \in \{0,1\}^n} -\sum_{(i,j) \in E} w_{ij}(x_i + x_j - 2x_i x_j)

Pour faciliter les calculs sur un simulateur, nous travaillons avec un graphe à quatre nœuds.

Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique

Nous définissons le problème de la coupe maximale à l'aide de la Maxcut classe d'application de qiskit-addon-opt-mapper, qui permet de construire la formulation QUBO directement à partir d'un graphe. Nous le convertissons ensuite en un QUBO et le traduisons en un hamiltonien d'Ising (SparsePauliOp) adapté à la QAOA. Nous résolvons également la relaxation continue du QUBO — en remplaçant la contrainte binaire xi{0,1}x_i \in \{0,1\} par xi[0,1]x_i \in [0,1] — afin d'obtenir le point de départ « warm-start » cc^*.

# Define a 4-node weighted graph for the max-cut problem
n_nodes = 4
edges = [(0, 1, 1.0), (0, 2, 1.0), (1, 2, 1.0), (1, 3, 1.0), (2, 3, 1.0)]

G = nx.Graph()
G.add_nodes_from(range(n_nodes))
G.add_weighted_edges_from(edges)

pos = nx.spring_layout(G, seed=42)
edge_labels = {(u, v): d["weight"] for u, v, d in G.edges(data=True)}

fig, ax = plt.subplots(figsize=(4, 3))
nx.draw(G, pos, with_labels=True, node_color="lightblue", ax=ax)
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, ax=ax)
ax.set_title("Max-Cut graph")
plt.tight_layout()
plt.show()

Output:

Output of the previous code cell

Le graphe comporte cinq arêtes. La partition « max-cut » optimale répartit les nœuds entre S={0,3}S = \{0, 3\} et Sˉ={1,2}\bar{S} = \{1, 2\} (ou son complément), coupant ainsi quatre des cinq arêtes, ce qui donne une valeur de coupure égale à 4.

# Build the max-cut problem directly from the NetworkX graph using the
# Maxcut application class. Internally it constructs the QUBO
#   minimize  -sum_{(i,j) in E} w_ij * (x_i + x_j - 2*x_i*x_j)
# (each edge contributes -w to the linear terms and +2w to the quadratic
# term), so we get the same OptimizationProblem without the boilerplate.
maxcut = Maxcut(G)
prob = maxcut.to_optimization_problem()
print(prob.prettyprint())

Output:

Problem name: Max-cut

Maximize
  -2*x_0*x_1 - 2*x_0*x_2 - 2*x_1*x_2 - 2*x_1*x_3 - 2*x_2*x_3 + 2*x_0 + 3*x_1
  + 3*x_2 + 2*x_3

Subject to
  No constraints

  Binary variables (4)
    x_0 x_1 x_2 x_3

Cette Maxcut classe encapsule la construction QUBO, ce qui nous évite d'avoir à développer manuellement l'objectif « max-cut ». L'objectif imprimé indique le coefficient linéaire de chaque variable (sa contribution individuelle à la découpe) et le coefficient quadratique de chaque terme croisé (la pénalité infligée lorsque deux nœuds adjacents se trouvent du même côté). La valeur sous-jacente OptimizationProblem renvoyée par to_optimization_problem() prend en charge les variables binaires, entières, continues et de type « spin », et correspond à l'objet attendu par les convertisseurs et les traducteurs utilisés à l'étape suivante.

# Convert the OptimizationProblem to a QUBO, then translate to an Ising Hamiltonian
#
# The substitution x_i = (1 - z_i)/2  maps binary variables to spin operators,
# yielding a Hamiltonian H_C = sum_i h_i Z_i + sum_{i<j} J_ij Z_i Z_j + constant.
# QAOA minimizes <H_C> to find the ground state, which encodes the optimal cut.
converter = OptimizationProblemToQubo()
qubo = converter.convert(prob)

cost_operator, offset = to_ising(qubo)
n_qubits = cost_operator.num_qubits

print(f"Cost Hamiltonian H_C ({n_qubits} qubits):")
print(cost_operator)
print(f"\nOffset (constant shift): {offset}")
print("  QUBO value = Ising energy + offset")

Output:

Cost Hamiltonian H_C (4 qubits):
SparsePauliOp(['IIZZ', 'IZIZ', 'IZZI', 'ZIZI', 'ZZII'],
              coeffs=[0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j])

Offset (constant shift): -2.5
  QUBO value = Ising energy + offset

Le to_ising traducteur renvoie un SparsePauliOp représentant HCH_C et un scalaire offset tel que QUBO value=HC+offset\text{QUBO value} = \langle H_C \rangle + \text{offset}. Pour ce problème de coupe maximale avec des poids unitaires, hi=0h_i = 0 pour tous les qubits (le graphe est symétrique en termes linéaires après la substitution xizix_i \to z_i ), et chaque arête apporte un couplage de type « ZiZjZ_i Z_j » d’intensité +0.5+0.5. La valeur propre minimale de HCH_C correspond à la coupe maximale.

# Solve the continuous (QP) relaxation to obtain the warm-start point c*
#
# The QP relaxation replaces the binary constraint x_i in {0,1} with x_i in [0,1]
# and minimizes the same quadratic objective. Its solution c*_i gives the
# probability that variable i should be 1 according to the classical relaxation.
#
# The max-cut QUBO has a non-convex quadratic matrix (negative eigenvalues),
# so the relaxed problem has multiple local minima. A naive single start from
# [0.5,...,0.5] converges to the symmetric saddle point c* = [0.5,...,0.5],
# which carries no useful structural information about the problem.
# Multi-start optimization is used to reliably find the global minimum.
Q = qubo.objective.quadratic.to_array(symmetric=True)
mu = qubo.objective.linear.to_array()


def qp_objective(x_cont):
    """Continuous relaxation of the QUBO objective."""
    return x_cont @ Q @ x_cont + mu @ x_cont + qubo.objective.constant


bounds = [(0.0, 1.0)] * n_qubits

rng = np.random.default_rng(42)
best_val = np.inf
c_star = None
for _ in range(200):
    x0 = rng.uniform(0.0, 1.0, n_qubits)
    result = minimize(qp_objective, x0, method="L-BFGS-B", bounds=bounds)
    if result.fun < best_val:
        best_val = result.fun
        c_star = result.x

print(f"QP relaxation solution c* = {np.round(c_star, 4)}")
print(f"QP objective value        = {best_val:.4f}")

Output:

QP relaxation solution c* = [1. 0. 0. 1.]
QP objective value        = -4.0000

Le solveur multi-démarrage trouve c=[1,0,0,1]c^* = [1, 0, 0, 1] (ou son complément [0,1,1,0][0, 1, 1, 0] ), qui correspond à la solution binaire optimale réelle. Pour ce problème, la relaxation QP est stricte : le minimum continu coïncide avec l'optimum entier, ce qui signifie que la relaxation identifie immédiatement la meilleure coupe. Après régularisation à l'aide de la méthode « ε=0.25\varepsilon = 0.25 » à l'étape 2, cette solution sera intégrée à l'état initial de « warm-start ».

Étape 2 : Optimiser le problème en vue de son exécution sur un matériel quantique

Nous construisons deux circuits QAOA et déterminons les angles de démarrage à chaud à partir de la solution QP.

Le modèle QAOA standard utilise la superposition uniforme +n|+\rangle^{\otimes n} comme état initial et le mélangeur standard XX -mixer HM=iXiH_M = -\sum_i X_i, implémenté sous la forme iRX(2β)\prod_i R_X(-2\beta) par couche.

La méthode QAOA à démarrage à chaud (WS-QAOA) décrite dans [1] apporte deux modifications structurelles par qubit ii :

  • État initial : RY(θi)0R_Y(\theta_i)|0\rangle avec θi=2arcsin(ci)\theta_i = 2\arcsin(\sqrt{c^*_i}); la probabilité de mesurer 1|1\rangle est donc égale à cic^*_i.
  • Mélangeur personnalisé : « RY(θi)RZ(2β)RY(θi)R_Y(\theta_i)\, R_Z(-2\beta)\, R_Y(-\theta_i) », dont l'état fondamental est « RY(θi)0R_Y(\theta_i)|0\rangle ». Cela signifie que le WS-QAOA se trouve dans l'état fondamental de son propre mélangeur, une propriété qu'il partage avec le QAOA standard, qui utilise l' +|+\rangle et le mélangeur « XX ».

p=1Remarque sur les couches : pour une seule couche QAOA, l'algorithme QAOA standard est analytiquement limité à environ 49 % de l'énergie optimale sur les graphes contenant des triangles (ce graphe comporte le triangle 0-1-2). Le « warm start » contourne cette limitation en intégrant directement les connaissances préalables sur la solution dans l'état initial.

# Number of QAOA layers (each layer = one cost unitary + one mixer unitary)
p = 1

# Regularization: clip c* to [epsilon, 1-epsilon] so no qubit is initialized
# in |0> or |1>, which would freeze it under the cost Hamiltonian.
epsilon = 0.25

c_clipped = np.clip(c_star, epsilon, 1 - epsilon)
thetas = 2 * np.arcsin(np.sqrt(c_clipped))

print(f"Continuous relaxation c*  = {np.round(c_star, 4)}")
print(f"After regularization      = {np.round(c_clipped, 4)}")
print(f"Warm-start angles theta   = {np.round(thetas, 4)} radians")
print()
print("Angle interpretation:")
print("  theta = 0      <->  c* = 0   (qubit points toward |0>)")
print(
    "  theta = pi/2   <->  c* = 0.5 (qubit in equal superposition, like |+>)"
)
print("  theta = pi     <->  c* = 1   (qubit points toward |1>)")

Output:

Continuous relaxation c*  = [1. 0. 0. 1.]
After regularization      = [0.75 0.25 0.25 0.75]
Warm-start angles theta   = [2.0944 1.0472 1.0472 2.0944] radians

Angle interpretation:
  theta = 0      <->  c* = 0   (qubit points toward |0>)
  theta = pi/2   <->  c* = 0.5 (qubit in equal superposition, like |+>)
  theta = pi     <->  c* = 1   (qubit points toward |1>)

Après découpage, c=1c^* = 1 devient 1ε=0.751 - \varepsilon = 0.75 et c=0c^* = 0 devient ε=0.25\varepsilon = 0.25. Les angles résultants θ[2.09,1.05,1.05,2.09]\theta \approx [2.09, 1.05, 1.05, 2.09] radians font pivoter fortement les qubits 0 et 3 vers 1|1\rangle et les qubits 1 et 2 vers 0|0\rangle, ce qui code directement la structure de la coupe optimale dans l'état quantique initial.

def apply_cost_unitary(qc, cost_op, gamma):
    """Apply exp(-i * gamma * H_C) to the circuit.

    Each Pauli term in H_C contributes a rotation gate:
      - Single-Z term h_i * Z_i  ->  RZ(2 * gamma * h_i) on qubit i
      - Two-Z term J_ij * Z_i Z_j  ->  CNOT, RZ(2 * gamma * J_ij), CNOT
    """
    for pauli_term, coeff in zip(cost_op.paulis, cost_op.coeffs):
        indices = [
            j for j, q in enumerate(pauli_term.to_label()[::-1]) if q == "Z"
        ]
        if len(indices) == 1:
            qc.rz(2 * gamma * coeff.real, indices[0])
        elif len(indices) == 2:
            qc.cx(indices[0], indices[1])
            qc.rz(2 * gamma * coeff.real, indices[1])
            qc.cx(indices[0], indices[1])


def build_ws_qaoa(cost_op, n_layers, n_qubits, thetas):
    """WS-QAOA: warm-start initial state + custom per-qubit mixer.

    Per Egger et al. (2021) Eq. (1)-(2):
      Initial state per qubit i:  R_Y(theta_i) |0>
      Mixer gate per qubit i:     R_Y(theta_i) R_Z(-2*beta) R_Y(-theta_i)
    """
    gammas = ParameterVector("γ", n_layers)
    betas = ParameterVector("β", n_layers)
    qc = QuantumCircuit(n_qubits)
    for i, theta in enumerate(thetas):
        qc.ry(theta, i)  # warm-start initial state
    for k in range(n_layers):
        apply_cost_unitary(qc, cost_op, gammas[k])
        for i, theta in enumerate(thetas):
            qc.ry(theta, i)
            qc.rz(-2 * betas[k], i)
            qc.ry(-theta, i)
    return qc, gammas, betas


# Standard QAOA via the Qiskit built-in helper:
# qaoa_ansatz prepares |+>^n, then alternates exp(-i*gamma*H_C) with the
# default X-mixer for `reps` layers. The returned circuit exposes the
# variational parameters via std_qc.parameters.
std_qc = qaoa_ansatz(cost_operator, reps=p)

# WS-QAOA: keep the custom builder. The per-qubit mixer
# R_Y(theta_i) R_Z(-2*beta) R_Y(-theta_i) is implemented as an explicit gate
# sequence rather than as a SparsePauliOp, so we construct the circuit
# directly to stay close to the Egger et al. (2021) formulation.
ws_qc, ws_gammas, ws_betas = build_ws_qaoa(cost_operator, p, n_qubits, thetas)

Pour l'approche standard, nous renvoyons à qaoa_ansatz, qui construit l' +n|+\rangle^{\otimes n} e, applique l'opérateur unitaire de coût et utilise le mélangeur « XX » par défaut pour chacune des reps couches. Pour WS-QAOA, nous conservons l'aide explicite build_ws_qaoa car l' RY(θ)RZ(2β)RY(θ)R_Y(\theta)\,R_Z(-2\beta)\,R_Y(-\theta) du mélangeur par qubit est exprimée sous la forme d'une séquence de portes plutôt que sous la forme d'une somme de Paulis. Cet apply_cost_unitary outil lit directement à partir de SparsePauliOp l'hamiltonien; il permet donc de traiter n'importe quel problème QUBO sans avoir à construire manuellement de circuit.

print("Standard QAOA circuit (p=1):")
std_qc.draw("mpl", fold=-1)

Output:

Standard QAOA circuit (p=1):
Output of the previous code cell
print("\nWS-QAOA circuit (p=1):")
ws_qc.draw("mpl", fold=-1)

Output:


WS-QAOA circuit (p=1):
Output of the previous code cell

Les deux circuits suivent la même structure : une couche initiale de préparation d'état, suivie d' pp e alternance de couches « cost-unitary » et « mixer-unitary ». Dans le circuit WS-QAOA, les portes d’ouverture RYR_Y codent cc^*, et le mélangeur remplace chaque RXR_X par un triplet conjugué RYR_YRZR_ZRYR_Y. La différence de profondeur entre les deux circuits augmente de manière linéaire avec l' pp e, mais reste gérable à faible profondeur.

Étape 3 : Exécutez la commande à l'aide d' Qiskit primitives

Nous utilisons StatevectorEstimator pour une simulation exacte et sans bruit. La minimize fonction disponible sur SciPy, qui utilise l'optimiseur COBYLA, pilote la boucle variationnelle en appelant l'estimateur à chaque itération afin d'évaluer HC\langle H_C \rangle pour un ensemble de paramètres donné (γ,β)(\gamma, \beta).

Les deux algorithmes utilisent des paramètres initiaux différents qui reflètent les connaissances dont chacun dispose avant l'optimisation :

  • QAOA standard : initialisation aléatoire dans [0,π][0, \pi] — ce qui est approprié puisqu’aucune information structurelle n’est disponible.
  • WS-QAOA : γ=0\gamma = 0, β=π/4\beta = \pi/4 — sur γ=0\gamma=0, le coût unitaire correspond à l'identité; ainsi, la toute première évaluation du circuit effectue un échantillonnage direct à partir de l'état initial de « warm-start ». Cela donne à COBYLA un signal de départ solide, en phase avec la solution classique.
estimator = StatevectorEstimator()


def make_cost_fn(circuit, param_order, cost_op, estimator, history):
    """Return a scalar cost function compatible with scipy.optimize.minimize."""

    def cost_fn(params):
        bound = circuit.assign_parameters(dict(zip(param_order, params)))
        job = estimator.run([(bound, cost_op)])
        energy = job.result()[0].data.evs.real
        history.append(energy)
        return energy

    return cost_fn


# Standard QAOA: random initialization
np.random.seed(42)
std_param_order = list(std_qc.parameters)
std_params0 = np.random.uniform(0, np.pi, len(std_param_order))
std_history = []

std_result = minimize(
    make_cost_fn(
        std_qc, std_param_order, cost_operator, estimator, std_history
    ),
    std_params0,
    method="COBYLA",
    options={"maxiter": 300, "rhobeg": 0.5},
)
print(f"Standard QAOA optimal energy : {std_result.fun:.4f}")
print(f"  optimal params: {std_result.x.round(4)}")
print(f"  optimizer calls: {len(std_history)}")


# WS-QAOA: informed initialization
ws_params0 = np.concatenate([np.zeros(p), np.full(p, np.pi / 4)])
ws_history = []
ws_param_order = list(ws_gammas) + list(ws_betas)

ws_result = minimize(
    make_cost_fn(ws_qc, ws_param_order, cost_operator, estimator, ws_history),
    ws_params0,
    method="COBYLA",
    options={"maxiter": 300, "rhobeg": 0.5},
)
print(f"\nWS-QAOA optimal energy       : {ws_result.fun:.4f}")
print(
    f"  optimal params: gamma={ws_result.x[:p].round(4)}, beta={ws_result.x[p:].round(4)}"
)
print(f"  optimizer calls: {len(ws_history)}")

Output:

Standard QAOA optimal energy : -0.5859
  optimal params: [0.6803 2.0533]
  optimizer calls: 47

WS-QAOA optimal energy       : -1.5000
  optimal params: gamma=[-0.0001], beta=[1.5708]
  optimizer calls: 42

Grâce à la base de départ éclairée de WS-QAOA, COBYLA commence avec une valeur d'énergie significative proche de la solution de « démarrage à chaud », tandis que la méthode QAOA standard part d'un point essentiellement aléatoire sur le paysage énergétique. Cette différence de niveau de départ est le principal facteur à l'origine de l'écart de convergence observable à l'étape 4.

# Compute the exact optimal energy by brute-force over all 2^n bitstrings
all_energies = [
    Statevector.from_label(format(k, f"0{n_qubits}b"))
    .expectation_value(cost_operator)
    .real
    for k in range(2**n_qubits)
]
optimal_energy = min(all_energies)

print(f"Exact optimal energy         : {optimal_energy:.4f}")
print(f"Standard QAOA approx. ratio  : {std_result.fun / optimal_energy:.4f}")
print(f"WS-QAOA approx. ratio        : {ws_result.fun / optimal_energy:.4f}")

Output:

Exact optimal energy         : -1.5000
Standard QAOA approx. ratio  : 0.3906
WS-QAOA approx. ratio        : 1.0000

Le rapport d'approximation est défini comme suit : HCQAOA/Eopt\langle H_C \rangle_{\text{QAOA}} / E_{\text{opt}}. Pour les problèmes de minimisation où Eopt<0E_{\text{opt}} < 0, un rapport proche de 1 signifie que l'algorithme a trouvé une énergie plus faible (une meilleure solution). La recherche par force brute sur l'ensemble des états de base d' 2n2^n s n'est réalisable que pour les petites nn s et sert de référence de référence.

Étape 4 : Traitement ultérieur et restitution du résultat dans le format classique souhaité

Nous visualisons la convergence, échantillonnons les circuits optimisés pour obtenir des solutions sous forme de chaînes de bits, décodons ces chaînes de bits pour les reconvertir en partitions de coupe maximale, puis résumons les résultats finaux.

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(std_history, label="Standard QAOA", alpha=0.85)
ax.plot(ws_history, label="WS-QAOA", alpha=0.85)
ax.axhline(
    optimal_energy,
    color="k",
    linestyle="--",
    label=f"Exact optimal ({optimal_energy:.2f})",
)
ax.set_xlabel("Optimizer call")
ax.set_ylabel(r"$\langle H_C \rangle$")
ax.set_title("Convergence: Standard QAOA vs. WS-QAOA")
ax.legend()
plt.tight_layout()
plt.show()

Output:

Output of the previous code cell

Le graphique de convergence montre l' HC\langle H_C \rangle d'énergie à chaque évaluation de la fonction COBYLA. Le QAOA standard, disponible sur p=1p=1, se limite à environ 49 % de l’énergie optimale sur ce graphe (le maximum théorique pour un QAOA de type « p=1p=1 » sur des graphes comportant des triangles), et se stabilise autour de 0.74-0.74. Le WS-QAOA, initialisé près de la solution optimale, converge rapidement vers 1.50-1.50 (l’optimum exact) avec beaucoup moins d’itérations. Cela illustre l'avantage majeur du « warm start » : à profondeur de circuit égale, il permet d'obtenir une solution nettement meilleure.

# Sample the optimized circuits to recover the most probable bitstring solutions
sampler = StatevectorSampler()
shots = 1024


def get_best_bitstring(circuit, param_order, optimal_params, sampler, shots):
    bound = circuit.assign_parameters(dict(zip(param_order, optimal_params)))
    bound.measure_all()
    job = sampler.run([bound], shots=shots)
    counts = job.result()[0].data.meas.get_counts()
    return max(counts, key=counts.get), counts


def evaluate_cut(bitstring, G):
    """Compute the Max-Cut value for a bitstring node assignment."""
    x = [int(b) for b in bitstring]
    cut_val = sum(
        w for u, v, w in G.edges.data("weight", default=1) if x[u] != x[v]
    )
    set0 = [i for i, b in enumerate(bitstring) if b == "0"]
    set1 = [i for i, b in enumerate(bitstring) if b == "1"]
    return cut_val, set0, set1


# Qiskit bitstring ordering: rightmost character = qubit 0
def decode_bitstring(bs):
    return bs[::-1]


std_best, std_counts = get_best_bitstring(
    std_qc, std_param_order, std_result.x, sampler, shots
)
ws_best, ws_counts = get_best_bitstring(
    ws_qc, ws_param_order, ws_result.x, sampler, shots
)

std_cut, std_s0, std_s1 = evaluate_cut(decode_bitstring(std_best), G)
ws_cut, ws_s0, ws_s1 = evaluate_cut(decode_bitstring(ws_best), G)

print(f"Standard QAOA most-probable bitstring : {std_best}")
print(f"  Partition: S={std_s0}, S̄={std_s1}  |  cut value = {std_cut}")
print()
print(f"WS-QAOA most-probable bitstring       : {ws_best}")
print(f"  Partition: S={ws_s0}, S̄={ws_s1}  |  cut value = {ws_cut}")

Output:

Standard QAOA most-probable bitstring : 0110
  Partition: S=[0, 3], S̄=[1, 2]  |  cut value = 4.0

WS-QAOA most-probable bitstring       : 0110
  Partition: S=[0, 3], S̄=[1, 2]  |  cut value = 4.0

Les chaînes de bits provenant de Sampler sont renvoyées avec le qubit 0 à l'extrême droite; ainsi, en inversant la chaîne, on associe l'index ii à la variable xix_i. La valeur de la coupure correspond au poids total des arêtes qui traversent la partition, ce que le problème de la coupure maximale cherche à maximiser. Une valeur de découpage égale à 4 utilise quatre des cinq arêtes disponibles, ce qui correspond au maximum théorique pour ce graphe.

# Visualize the WS-QAOA solution on the graph
fig, axes = plt.subplots(1, 2, figsize=(8, 3))

for ax, s0, s1, cut, title in [
    (axes[0], std_s0, std_s1, std_cut, f"Standard QAOA (cut = {std_cut})"),
    (axes[1], ws_s0, ws_s1, ws_cut, f"WS-QAOA (cut = {ws_cut})"),
]:
    colors = ["skyblue" if i in s0 else "salmon" for i in G.nodes()]
    nx.draw(G, pos, with_labels=True, node_color=colors, ax=ax)
    nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, ax=ax)
    ax.set_title(title)

plt.tight_layout()
plt.show()

# Summary
# to_ising offset: QUBO value = Ising energy + offset, so Max-Cut value = -(Ising energy + offset)
optimal_cut = -(optimal_energy + offset)
print("=== Summary ===")
print(
    f"{'Method':<20} {'Ising energy':>14} {'Cut value':>12} {'Approx. ratio':>15}"
)
print("-" * 65)
print(
    f"{'Standard QAOA':<20} {std_result.fun:>14.4f} {std_cut:>12} {std_result.fun/optimal_energy:>15.4f}"
)
print(
    f"{'WS-QAOA':<20} {ws_result.fun:>14.4f} {ws_cut:>12} {ws_result.fun/optimal_energy:>15.4f}"
)
print(
    f"{'Exact optimal':<20} {optimal_energy:>14.4f} {optimal_cut:>12.0f} {'1.0000':>15}"
)

Output:

Output of the previous code cell
=== Summary ===
Method                 Ising energy    Cut value   Approx. ratio
-----------------------------------------------------------------
Standard QAOA               -0.5859          4.0          0.3906
WS-QAOA                     -1.5000          4.0          1.0000
Exact optimal               -1.5000            4          1.0000

Dans la visualisation graphique, chaque nœud est coloré en fonction de la partition à laquelle il appartient (bleu = SS, orange = Sˉ\bar{S} ). Les arêtes qui traversent la partition (reliant des nœuds de couleurs différentes) sont celles qui sont prises en compte dans la coupure.

Ces deux méthodes trouvent une chaîne binaire dont la valeur de coupure est 4, mais pour des raisons très différentes. Il est important de noter que le graphique de convergence et la chaîne binaire échantillonnée mesurent deux choses différentes :

  • Le graphique de convergence suit l' HC\langle H_C \rangle énergétique moyenne de l'état quantique complet, qui correspond à une moyenne pondérée de toutes les chaînes de bits présentes dans la superposition. L'algorithme QAOA standard converge vers environ 0.62-0.62, ce qui est bien supérieur à l' 1.50-1.50 optimale, ce qui signifie que son état quantique est réparti sur de nombreuses chaînes de bits sous-optimales et ne contient que rarement la bonne réponse.
  • La chaîne de bits échantillonnée correspond à un seul tirage issu de cet état. La méthode QAOA standard a eu de la chance dans ce cas : la partition optimale s'est avérée être le résultat le plus fréquemment observé, même à partir d'un état diffus. Face à des problèmes plus complexes, à du matériel plus bruyant ou à un plus grand nombre de solutions candidates en concurrence, cette chance finit par s'épuiser.

WS-QAOA, en revanche, fait converger son énergie moyenne jusqu’à 1.50-1.50, ce qui signifie que son état quantique est concentré sur les chaînes de bits optimales. Presque chaque essai donne la bonne réponse; la solution est donc trouvée de manière fiable et non par hasard.

Conséquence pratique : sur ce petit simulateur silencieux, la différence peut sembler minime, mais pour des problèmes de plus grande envergure ou sur du matériel réel, un état dont l'énergie moyenne est proche de l'optimum est bien plus robuste qu'un état qui ne trouve que de manière occasionnelle la bonne réponse à partir d'une distribution diffuse.

# Compare the full probability distribution over cut values for both
# algorithms. The most-probable bitstring above only reveals the mode;
# this histogram exposes how much of the quantum state's probability mass
# lands on the optimal cut versus on suboptimal partitions.
def cut_value_distribution(counts, G, shots):
    dist = {}
    for bs, c in counts.items():
        cut, _, _ = evaluate_cut(decode_bitstring(bs), G)
        dist[cut] = dist.get(cut, 0.0) + c / shots
    return dist


std_cut_dist = cut_value_distribution(std_counts, G, shots)
ws_cut_dist = cut_value_distribution(ws_counts, G, shots)

cut_values = sorted(set(std_cut_dist) | set(ws_cut_dist))
std_probs = [std_cut_dist.get(c, 0.0) for c in cut_values]
ws_probs = [ws_cut_dist.get(c, 0.0) for c in cut_values]

fig, ax = plt.subplots(figsize=(7, 4))
x = np.arange(len(cut_values))
width = 0.4
ax.bar(
    x - width / 2, std_probs, width, label="Standard QAOA", color="steelblue"
)
ax.bar(x + width / 2, ws_probs, width, label="WS-QAOA", color="salmon")
ax.axvline(
    cut_values.index(optimal_cut),
    color="k",
    linestyle="--",
    alpha=0.4,
    label=f"Optimal cut = {optimal_cut:g}",
)
ax.set_xticks(x)
ax.set_xticklabels([f"{c:g}" for c in cut_values])
ax.set_xlabel("Cut value")
ax.set_ylabel("Probability")
ax.set_title(f"Probability of measuring each cut value ({shots} shots)")
ax.legend()
plt.tight_layout()
plt.show()

print(
    f"P(cut = {optimal_cut:g}) | Standard QAOA = "
    f"{std_cut_dist.get(optimal_cut, 0):.4f}  "
    f"WS-QAOA = {ws_cut_dist.get(optimal_cut, 0):.4f}"
)

Output:

Output of the previous code cell
P(cut = 4) | Standard QAOA = 0.4639  WS-QAOA = 1.0000

Cet histogramme quantifie ce que le graphique de convergence ne faisait que suggérer. Dans l'algorithme QAOA standard, la probabilité est répartie entre plusieurs valeurs de coupure sous-optimales; par conséquent, la probabilité d'obtenir une coupure optimale de quatre en un seul essai ne représente qu'une fraction de la masse totale. WS-QAOA concentre la quasi-totalité de sa probabilité sur la solution optimale, de sorte que presque chaque tentative donne la bonne réponse. C'est là la marque distinctive d'un état dont l'énergie moyenne a convergé vers l'énergie de l'état fondamental, par opposition à un état qui se trouve simplement inclure l'état fondamental dans une superposition large.

Exemple de matériel à grande échelle

Regrouper les étapes 1 à 4 en un seul bloc de code

# Selecting a backend using real hardware
service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)
print(f"Using backend: {backend.name}")

Output:

Using backend: ibm_boston
# ── Step 1a: Build the 40-node Max-Cut problem ─────────────────────────────
# A 3-regular graph (every node has exactly 3 neighbors) is a standard QAOA
N_LARGE = 40
G_large = nx.random_regular_graph(d=3, n=N_LARGE, seed=0)
edges_large = list(G_large.edges())
print(f"Graph: {N_LARGE} nodes, {len(edges_large)} edges (3-regular)")

# Visualize the graph so it is clear what problem we are solving before any
# quantum work. Nodes in a circular layout; each edge contributes +1 to the
# cut value when its endpoints land in different partitions.
pos_large = nx.circular_layout(G_large)
fig, ax = plt.subplots(figsize=(6, 6))
nx.draw(
    G_large,
    pos_large,
    with_labels=True,
    node_color="lightblue",
    node_size=400,
    font_size=7,
    ax=ax,
)
ax.set_title(f"40-node 3-regular Max-Cut graph ({len(edges_large)} edges)")
plt.tight_layout()
plt.show()


# Same Maxcut → OptimizationProblem → QUBO → Ising pipeline as the small example,
# applied to the 40-node graph.
prob_large = Maxcut(G_large).to_optimization_problem()
converter_large = OptimizationProblemToQubo()
qubo_large = converter_large.convert(prob_large)
cost_op_large, offset_large = to_ising(qubo_large)
n_qubits_large = cost_op_large.num_qubits
print(
    f"Cost operator: {n_qubits_large} qubits, {len(cost_op_large)} Pauli terms"
)

# ── Step 1b: QP relaxation (multi-start L-BFGS-B) ─────────────────────────
# Same multi-start approach as the small example. At 40 qubits the relaxed
# landscape has many more local minima, so 200 random starts are essential
# to find a low-energy warm-start point.
Q_large = qubo_large.objective.quadratic.to_array(symmetric=True)
mu_large = qubo_large.objective.linear.to_array()


def qp_obj_large(x):
    return x @ Q_large @ x + mu_large @ x + qubo_large.objective.constant


bounds_large = [(0.0, 1.0)] * n_qubits_large
rng_qp = np.random.default_rng(42)
best_val_large, c_star_large = np.inf, None

for _ in range(200):
    x0 = rng_qp.uniform(0.0, 1.0, n_qubits_large)
    res = minimize(qp_obj_large, x0, method="L-BFGS-B", bounds=bounds_large)
    if res.fun < best_val_large:
        best_val_large, c_star_large = res.fun, res.x

# Regularize and convert to rotation angles (same formula as small example)
epsilon_large = 0.25
c_clipped_large = np.clip(c_star_large, epsilon_large, 1 - epsilon_large)
thetas_large = 2 * np.arcsin(np.sqrt(c_clipped_large))
print(
    f"c* range: [{c_star_large.min():.3f}, {c_star_large.max():.3f}]  "
    f"theta range: [{thetas_large.min():.3f}, {thetas_large.max():.3f}] rad"
)

# Plot the distribution of c* values to see how much structure the relaxation
# extracted. Values near 0/1 mean confident assignments; values near 0.5 mean
# the classical solver was uncertain and quantum exploration is most needed there.
fig, ax = plt.subplots(figsize=(6, 3))
ax.hist(c_star_large, bins=20, color="steelblue", edgecolor="white")
ax.axvline(0.5, color="k", linestyle="--", label="Uniform prior (std QAOA)")
ax.set_xlabel(r"$c^*_i$")
ax.set_ylabel("Count")
ax.set_title(r"Distribution of warm-start values $c^*_i$ (40-node graph)")
ax.legend()
plt.tight_layout()
plt.show()

# ── Step 1c: Build WS-QAOA circuit ─────────────────────────────────────────
# Reuse build_ws_qaoa from the small-scale section unchanged; the helper
# scales automatically with n_qubits and the cost operator size.
p_large = 1
ws_qc_large, ws_gammas_large, ws_betas_large = build_ws_qaoa(
    cost_op_large, p_large, n_qubits_large, thetas_large
)
ws_qc_large.measure_all()

# ── Step 2: Transpile to hardware-native gates ──────────────────────────
# generate_preset_pass_manager compiles the abstract circuit to th
# gate set of the backend and inserts SWAP gates wherever the cost Hamiltonian
# couples qubits that are not directly connected on the processor.
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
ws_isa_large = pm.run(ws_qc_large)

ecr_count = ws_isa_large.count_ops().get("ecr", 0)
print(
    f"\nTranspiled circuit: 2Q depth={ws_isa_large.depth(lambda x: x.operation.num_qubits == 2)}"
)
ws_isa_large.draw("mpl", fold=-1)

Output:

Graph: 40 nodes, 60 edges (3-regular)
Output of the previous code cell
Cost operator: 40 qubits, 60 Pauli terms
c* range: [0.000, 1.000]  theta range: [1.047, 2.094] rad
Output of the previous code cell

Transpiled circuit: 2Q depth=86
Output of the previous code cell
# ── Classical baseline via simulated annealing ────────────────────
# Run SA before any hardware calls to get a strong classical reference cut
# value. SA is fast (seconds), needs no solver license, and reliably finds
# near-optimal solutions on 40-node graphs. We use sa_cut as the denominator
# for the approximation ratio instead of the looser QP upper bound.
#
# At each step we flip a random node and accept the move if it improves the
# cut, or with probability exp(delta/T) otherwise. Temperature T decays
# geometrically, allowing uphill moves early on to escape local minima.
def simulated_annealing_maxcut(
    G, seed=0, T0=2.0, T_min=1e-4, alpha=0.995, n_steps=100_000
):
    rng_sa = np.random.default_rng(seed)
    n = G.number_of_nodes()
    x = rng_sa.integers(0, 2, n)
    best_x = x.copy()
    best_cut = sum(1 for u, v in G.edges() if x[u] != x[v])
    T = T0
    for _ in range(n_steps):
        i = rng_sa.integers(0, n)
        delta = sum((-1 if x[i] != x[nb] else 1) for nb in G.neighbors(i))
        if delta > 0 or rng_sa.random() < np.exp(delta / T):
            x[i] ^= 1
            cut = sum(1 for u, v in G.edges() if x[u] != x[v])
            if cut > best_cut:
                best_cut, best_x = cut, x.copy()
        T = max(T * alpha, T_min)
    return best_x, best_cut


sa_solution, sa_cut = simulated_annealing_maxcut(G_large)
print(f"Simulated annealing cut value: {sa_cut}  (classical reference)")

# ── Step 3: Execution on hardware ───────────────────────────
# A Session reserves the backend so the COBYLA iterations and final sampling
# run back-to-back without re-queuing between jobs — important when the
# optimizer submits many short jobs sequentially. All jobs are tagged with
# "TUT_WSQAOA" for traceability in the IBM Quantum dashboard.
#
# EstimatorV2 with resilience_level=1 enables twirled readout error extinction
# (TREX), which corrects systematic measurement bit-flip errors without extra
# circuit overhead. 4096 shots per call balances estimation noise vs. job time.
estimator_options = EstimatorOptions()
estimator_options.resilience_level = 1
estimator_options.default_shots = 4096
estimator_options.environment.job_tags = ["TUT_WSQAOA"]

# Align the cost observable with the physical qubit layout chosen by the transpiler
cost_op_isa = cost_op_large.apply_layout(ws_isa_large.layout)
ws_param_order_isa = list(ws_isa_large.parameters)

ws_history_hw = []

with Session(backend=backend) as session:
    estimator_hw = Estimator(mode=session, options=estimator_options)

    def hw_cost_fn(params):
        bound = ws_isa_large.assign_parameters(
            dict(zip(ws_param_order_isa, params))
        )
        energy = (
            estimator_hw.run([(bound, cost_op_isa)]).result()[0].data.evs.real
        )
        ws_history_hw.append(float(energy))
        print(
            f"  iter {len(ws_history_hw):>3d}  <H_C> = {energy:.4f}", end="\r"
        )
        return float(energy)

    # Warm-start initialization: gamma=0 means the cost unitary is the identity on
    # the first call, so COBYLA immediately evaluates the warm-start state itself —
    # a much better starting signal than a random point.
    ws_params0_hw = np.concatenate(
        [np.zeros(p_large), np.full(p_large, np.pi / 4)]
    )

    ws_result_hw = minimize(
        hw_cost_fn,
        ws_params0_hw,
        method="COBYLA",
        options={"maxiter": 150, "rhobeg": 0.3},
    )
    print(
        f"\nOptimization complete: energy={ws_result_hw.fun:.4f}, "
        f"iterations={len(ws_history_hw)}"
    )

    # ── Step 3b: Sample the optimized circuit ──────────────────────────────────
    # Use 8192 shots for the final sample to get a reliable mode estimate.
    sampler_hw = Sampler(
        mode=session,
        options={"environment": {"job_tags": ["TUT_WSQAOA"]}},
    )
    ws_bound_hw = ws_isa_large.assign_parameters(
        dict(zip(ws_param_order_isa, ws_result_hw.x))
    )
    counts_hw = (
        sampler_hw.run([ws_bound_hw], shots=8192)
        .result()[0]
        .data.meas.get_counts()
    )

best_bs_hw = max(counts_hw, key=counts_hw.get)
best_count = counts_hw[best_bs_hw]
total_shots = sum(counts_hw.values())

# Decode: Qiskit returns bitstrings with qubit 0 at the rightmost position,
# so reversing the string maps character index i to variable x_i.
cut_val_hw, s0_hw, s1_hw = evaluate_cut(best_bs_hw[::-1], G_large)

# Compare against simulated annealing.
# A ratio >= 1.0 means WS-QAOA matched or beat the classical SA solution.
# A ratio close to 1.0 (e.g. > 0.95) shows the quantum result is competitive.
approx_ratio_hw = cut_val_hw / sa_cut
print(
    f"Most-probable bitstring frequency: {best_count}/{total_shots} "
    f"({100*best_count/total_shots:.1f}%)"
)
print(
    f"WS-QAOA cut: {cut_val_hw}  |  SA cut: {sa_cut}  "
    f"|  Approximation ratio vs SA: {approx_ratio_hw:.4f}"
)

# Visualize both solutions side-by-side on the graph.
# Blue = partition S, orange = partition S-bar.
# Edges crossing between colors are the ones counted in the cut.
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
for ax, assignment, cut, title in [
    (
        axes[0],
        list(sa_solution),
        sa_cut,
        f"Simulated Annealing (cut={sa_cut})",
    ),
    (
        axes[1],
        [int(b) for b in best_bs_hw[::-1]],
        cut_val_hw,
        f"WS-QAOA hardware (cut={cut_val_hw})",
    ),
]:
    colors = [
        "skyblue" if assignment[i] == 0 else "salmon" for i in G_large.nodes()
    ]
    nx.draw(
        G_large,
        pos_large,
        with_labels=True,
        node_color=colors,
        node_size=400,
        font_size=7,
        ax=ax,
    )
    ax.set_title(title)
plt.suptitle("Max-Cut partitions: SA vs WS-QAOA", fontsize=13)
plt.tight_layout()
plt.show()

# ── Step 4: Convergence plot and summary ──────────────────────────────────
# On real hardware the trace will be noisy (shot noise + gate errors), but the
# overall downward trend confirms that COBYLA is making progress despite noise.
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(ws_history_hw, color="tab:orange", label="WS-QAOA (hardware)")
ax.axhline(
    ws_result_hw.fun,
    color="tab:orange",
    linestyle=":",
    label=f"Final energy ({ws_result_hw.fun:.3f})",
)
ax.set_xlabel("Optimizer call")
ax.set_ylabel(r"$\langle H_C \rangle$")
ax.set_title(f"WS-QAOA convergence on {backend.name} (40 qubits, p=1)")
ax.legend()
plt.tight_layout()
plt.show()


print("\n=== Large Scale Summary ===")
print(f"{'Metric':<38} {'Value':>10}")
print("-" * 50)
print(f"{'Nodes / Edges':<38} {N_LARGE:>5} / {len(edges_large):<4}")
print(f"{'QAOA layers (p)':<38} {p_large:>10}")
print(f"{'Transpiled ECR gate count':<38} {ecr_count:>10}")
print(f"{'Transpiled circuit depth':<38} {ws_isa_large.depth():>10}")
print(f"{'Optimizer iterations':<38} {len(ws_history_hw):>10}")
print(f"{'WS-QAOA energy (hardware)':<38} {ws_result_hw.fun:>10.4f}")
print(f"{'Cut value':<38} {cut_val_hw:>10}")
print(f"{'Simulated annealing cut value':<38} {sa_cut:>10}")
print(f"{'Approximation ratio (vs SA)':<38} {approx_ratio_hw:>10.4f}")

Output:

Simulated annealing cut value: 53  (classical reference)
  iter  31  <H_C> = -12.4094
Optimization complete: energy=-13.0256, iterations=31
Most-probable bitstring frequency: 4/8192 (0.0%)
WS-QAOA cut: 53  |  SA cut: 53  |  Approximation ratio vs SA: 1.0000
Output of the previous code cell Output of the previous code cell

=== Large Scale Summary ===
Metric                                      Value
--------------------------------------------------
Nodes / Edges                             40 / 60  
QAOA layers (p)                                 1
Transpiled ECR gate count                       0
Transpiled circuit depth                      276
Optimizer iterations                           31
WS-QAOA energy (hardware)                -13.0256
Cut value                                      53
Simulated annealing cut value                  53
Approximation ratio (vs SA)                1.0000

Etapes suivantes

Recommandations

Si ce travail vous a paru intéressant, les documents suivants pourraient vous intéresser :

  • Couches QAOA supérieures : augmentez p le nombre de couches pour observer comment les deux algorithmes s'améliorent à mesure que le nombre de couches du circuit augmente, et pour voir si l'avantage du WS-QAOA à faible profondeur persiste.
  • Module d'optimisation « Mapper » de Qiskit : consultez la documentation et essayez de modéliser différents problèmes combinatoires, ou d'utiliser différents solveurs pour la relaxation continue.

Références

[1] D. J. Egger, J. Mareček et S. Woerner, « Warm-starting quantum optimization », Quantum, vol. 5, p. 479, 2021. arXiv:2009.10095

[2] E. Farhi, J. Goldstone et S. Gutmann, « A quantum approximate optimization algorithm », arXiv:1411.4028, 2014.

Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.