{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "2a246d14-ed2b-4573-bf97-939c3628b3bb",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Boucles d'optimisation\"\n",
        "description: \"Cette leçon explique comment utiliser les boucles d'optimisation classiques pour intégrer des paramètres dans les calculs quantiques.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore nabla */}\n",
        "\n",
        "<span id=\"optimization-loops\" />\n",
        "\n",
        "# Boucles d'optimisation\n",
        "\n",
        "Au cours de cette leçon, nous apprendrons à utiliser un *optimiseur* pour explorer de manière itérative les états quantiques paramétrés de notre ansatz :\n",
        "\n",
        "* Création d'une boucle d'optimisation\n",
        "* Comprendre les compromis lors de l'utilisation d'optimiseurs locaux et globaux\n",
        "* Explorer les plateaux stériles et comment les éviter\n",
        "\n",
        "À un niveau élevé, les optimiseurs sont essentiels à l'exploration de notre espace de recherche. L'optimiseur utilise les évaluations de la fonction de coût pour sélectionner l'ensemble suivant de paramètres dans une boucle variationnelle, et répète le processus jusqu'à ce qu'il atteigne un état stable. À ce stade, un ensemble optimal de valeurs de paramètres $\\vec\\theta^*$ est renvoyé.\n",
        "\n",
        "![Un diagramme de certains facteurs importants dans l'optimisation, y compris les plateaux stériles, les optimiseurs avec ou sans gradient, et le bootstrapping.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-workflow.svg)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "110421e7-0d2a-4653-a195-1925c809ca61",
      "metadata": {},
      "source": [
        "<span id=\"local-and-global-optimizers\" />\n",
        "\n",
        "## Optimiseurs locaux et globaux\n",
        "\n",
        "Nous commencerons par définir notre problème avant d'explorer chaque classe d'optimiseur. Nous commencerons par un circuit contenant huit paramètres variationnels :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "15cf7810-1d25-4c54-aff0-91d3a0c51cec",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/optimization-loops/extracted-outputs/15cf7810-1d25-4c54-aff0-91d3a0c51cec-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.circuit.library import TwoLocal\n",
        "import numpy as np\n",
        "\n",
        "theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()\n",
        "observable = SparsePauliOp.from_list([(\"XX\", 1), (\"YY\", -3)])\n",
        "\n",
        "reference_circuit = QuantumCircuit(2)\n",
        "reference_circuit.x(0)\n",
        "\n",
        "variational_form = TwoLocal(\n",
        "    2,\n",
        "    rotation_blocks=[\"rz\", \"ry\"],\n",
        "    entanglement_blocks=\"cx\",\n",
        "    entanglement=\"linear\",\n",
        "    reps=1,\n",
        ")\n",
        "ansatz = reference_circuit.compose(variational_form)\n",
        "\n",
        "ansatz.decompose().draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "950569c0-4e8f-49b5-b118-7cd227bd5ce1",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func_vqe(params, ansatz, hamiltonian, estimator):\n",
        "    \"\"\"Return estimate of energy from estimator\n",
        "\n",
        "    Parameters:\n",
        "        params (ndarray): Array of ansatz parameters\n",
        "        ansatz (QuantumCircuit): Parameterized ansatz circuit\n",
        "        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian\n",
        "        estimator (Estimator): Estimator primitive instance\n",
        "\n",
        "    Returns:\n",
        "        float: Energy estimate\n",
        "    \"\"\"\n",
        "    pub = (ansatz, hamiltonian, params)\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "f536ee50-373f-4873-b258-66c5c07c65c5",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.primitives import StatevectorEstimator\n",
        "\n",
        "estimator = StatevectorEstimator()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c9dcec4d-5b84-4731-8b5a-cb8a0f7572d5",
      "metadata": {},
      "source": [
        "<span id=\"local-optimizers\" />\n",
        "\n",
        "### Optimiseurs locaux\n",
        "\n",
        "Les optimiseurs locaux recherchent un point qui minimise la fonction de coût à partir d'un ou de plusieurs points initiaux ( $C(\\vec{\\theta_0})$ ) et se déplacent vers différents points en fonction de ce qu'ils observent dans la région qu'ils sont en train d'évaluer lors d'itérations successives. Cela signifie que la convergence de ces algorithmes est généralement rapide, mais qu'elle peut dépendre fortement du point initial. Les optimiseurs locaux sont incapables de voir au-delà de la région qu'ils évaluent et peuvent être particulièrement vulnérables aux minima locaux, signalant une convergence lorsqu'ils en trouvent un et ignorant d'autres états avec des évaluations plus favorables.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "447df742-13bd-4d4a-a364-41d34380cbc9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              " message: Optimization terminated successfully\n",
              " success: True\n",
              "  status: 0\n",
              "     fun: -3.9999999964520634\n",
              "       x: [ 1.000e+00  1.000e+00 -1.571e+00 -4.556e-05 -1.207e+00\n",
              "           -1.935e+00  4.079e-01 -4.079e-01]\n",
              "     nit: 12\n",
              "     jac: [ 0.000e+00  0.000e+00 -7.957e-04  2.543e-04  1.381e-03\n",
              "            1.381e-03  5.430e-04  5.431e-04]\n",
              "    nfev: 112\n",
              "    njev: 12"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"SLSQP\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "25fee3f4-9d3e-45ce-a104-dfde5e132b2b",
      "metadata": {},
      "source": [
        "<span id=\"global-optimizers\" />\n",
        "\n",
        "### Optimiseurs globaux\n",
        "\n",
        "Les optimiseurs globaux recherchent le point qui minimise la fonction de coût sur plusieurs régions de son domaine (c'est-à-dire non local), en l'évaluant itérativement (c'est-à-dire à l'itération $i$ ) sur un ensemble de vecteurs de paramètres $\\Theta_i := \\\\{ {\\vec\\theta_{i,j} | j \\in \\mathcal{J}_\\text{opt}^i} \\\\}$ déterminés par l'optimiseur. Cela les rend moins sensibles aux minima locaux et quelque peu indépendants de l'initialisation, mais aussi beaucoup plus lents à converger vers une solution proposée.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8d794038-8e75-4649-a978-a9e832ebed60",
      "metadata": {},
      "source": [
        "<span id=\"bootstrapping-optimization\" />\n",
        "\n",
        "### Optimisation par bootstrapping\n",
        "\n",
        "Le *bootstrapping*, ou la définition de la valeur initiale des paramètres $\\vec\\theta$ sur la base d'une optimisation antérieure, peut aider notre optimiseur à converger plus rapidement vers une solution. Nous appelons cela le point initial $\\vec\\theta_0$, et $|\\psi(\\vec\\theta_0)\\rangle = U_V(\\vec\\theta_0)|\\rho\\rangle$ l'état initial. Cet état initial diffère de notre état de référence $|\\rho\\rangle$, car le premier se concentre sur les paramètres initiaux définis au cours de notre boucle d'optimisation, tandis que le second se concentre sur l'utilisation de solutions \"de référence\" connues. Elles peuvent coïncider si $U_V(\\vec\\theta_0) \\equiv I$ (c'est-à-dire l'opération d'identité).\n",
        "\n",
        "Lorsque les optimiseurs locaux convergent vers des minima locaux non optimaux, nous pouvons essayer d'amorcer l'optimisation globalement et d'affiner la convergence localement. Bien que cela nécessite la mise en place de deux charges de travail variationnelles, cela permet à l'optimiseur de trouver une solution plus optimale que l'optimiseur local seul.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "07ba982e-9280-4e15-9d8c-4b6460b1deea",
      "metadata": {
        "gloss": {
          "hyperparameter": {
            "text": "A hyperparameter is a parameter that we use to control our algorithm. The ‘hyper’ distinguishes it from the parameters (θ) that our algorithm is trying to find.",
            "title": "Hyperparameter"
          },
          "local-minimum": {
            "text": "A local minimum is the lowest point of the function, for a small range of values of theta. In contrast, a global minimum is <i>the</i> lowest point, anywhere in our function (that is, for any value of theta). <a href='https://en.wikipedia.org/wiki/Maxima_and_minima'>Read more</a>.",
            "title": "Local minimum"
          }
        }
      },
      "source": [
        "<span id=\"gradient-based-and-gradient-free-optimizers\" />\n",
        "\n",
        "## Optimiseurs basés sur les gradients et sans gradients\n",
        "\n",
        "<span id=\"gradient-based\" />\n",
        "\n",
        "### Basé sur le gradient\n",
        "\n",
        "Pour notre fonction de coût $C(\\vec\\theta)$, si nous avons accès au gradient de la fonction $\\vec{\\nabla} C(\\vec\\theta)$ à partir d'un point initial, la manière la plus simple de minimiser la fonction est de mettre à jour les paramètres dans le sens de la descente la plus raide de la fonction. En d'autres termes, nous mettons à jour les paramètres en tant que $\\vec\\theta_{n+1} = \\vec\\theta_n - \\eta \\vec{\\nabla} C(\\vec\\theta)$, où $\\eta$ est le taux d'apprentissage - un petit <DefinitionTooltip definition=\"Un hyperparamètre est un paramètre que nous utilisons pour contrôler notre algorithme. Le terme hyper le distingue des paramètres (θ) que notre algorithme tente de trouver.\">hyperparamètre</DefinitionTooltip> positif qui contrôle la taille de la mise à jour. Nous continuons ainsi jusqu'à ce que nous convergions vers <DefinitionTooltip definition=\"Un minimum local est le point le plus bas de la fonction, pour une petite gamme de valeurs de thêta. En revanche, un minimum global est le point le plus bas, n'importe où dans notre fonction (c'est-à-dire pour n'importe quelle valeur de θ).\">minimum local</DefinitionTooltip> de la fonction de coût, $C({\\vec\\theta^*})$.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "0897260d-f3f8-4caa-8a79-f258e41f8e21",
      "metadata": {},
      "source": [
        "Nous pouvons utiliser cette fonction de coût et un optimiseur pour calculer les paramètres optimaux\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "aad81101-3c19-4946-8453-3db9df3369c0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "  message: Optimization terminated successfully.\n",
              "  success: True\n",
              "   status: 0\n",
              "      fun: -3.9999999999997025\n",
              "        x: [ 1.000e+00  1.000e+00  1.571e+00  3.220e-07  2.009e-01\n",
              "            -2.009e-01  6.342e-01 -6.342e-01]\n",
              "      nit: 14\n",
              "      jac: [-1.192e-07 -2.980e-08  8.345e-07  1.103e-06  5.960e-08\n",
              "             0.000e+00 -5.960e-08  2.980e-08]\n",
              " hess_inv: [[ 1.000e+00  1.872e-10 ...  5.077e-05  3.847e-05]\n",
              "            [ 1.872e-10  1.000e+00 ... -5.208e-05 -4.060e-05]\n",
              "            ...\n",
              "            [ 5.077e-05 -5.208e-05 ...  7.243e-01 -2.604e-01]\n",
              "            [ 3.847e-05 -4.060e-05 ... -2.604e-01  8.179e-01]]\n",
              "     nfev: 144\n",
              "     njev: 16"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"BFGS\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "0f7d55f8-e3a2-4223-b456-17b39a81f524",
      "metadata": {},
      "source": [
        "Les principaux inconvénients de ce type d'optimisation sont la vitesse de convergence, qui peut être très lente, et l'absence de garantie d'obtenir la solution optimale.\n",
        "\n",
        "![graphique de f(thêta) en fonction de thêta, plusieurs points montrent différents états d'un algorithme de descente de gradient recherchant le minimum d'une courbe.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-gradient-descent.svg)\n",
        "\n",
        "<span id=\"gradient-free\" />\n",
        "\n",
        "### Sans gradient\n",
        "\n",
        "Les algorithmes d'optimisation sans gradient ne nécessitent pas d'informations sur le gradient et peuvent être utiles dans les situations où le calcul du gradient est difficile, coûteux ou trop bruyant. Elles ont également tendance à être plus robustes dans la recherche d'optima globaux, alors que les méthodes basées sur le gradient ont tendance à converger vers des optima locaux. Nous allons explorer quelques cas où un optimiseur sans gradient peut aider à éviter les plateaux stériles. Cependant, les méthodes sans gradient nécessitent des ressources informatiques plus importantes, en particulier pour les problèmes avec des espaces de recherche de haute dimension.\n",
        "\n",
        "Voici un exemple qui utilise l'optimiseur [`COBYLA`](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html#optimize-minimize-cobyla) à la place de l'optimiseur :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "c749695b-3124-47d8-8a23-10c9d5965a7f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              " message: Optimization terminated successfully.\n",
              " success: True\n",
              "  status: 1\n",
              "     fun: -3.999999973369678\n",
              "       x: [ 1.631e+00  1.492e+00  1.571e+00  3.142e+00  1.375e+00\n",
              "           -1.767e+00  1.484e+00  1.658e+00]\n",
              "    nfev: 137\n",
              "   maxcv: 0.0"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"COBYLA\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "396d0617-9f20-48cb-b57c-30dfa54293ff",
      "metadata": {
        "gloss": {
          "barren-plateaus": {
            "text": "When gradients of parametrized quantum circuits become exponentially small with respect to the number of qubits, making optimization difficult and potentially impossible.",
            "title": "Barren Plateaus"
          }
        }
      },
      "source": [
        "<span id=\"barren-plateaus\" />\n",
        "\n",
        "## Plateaux arides\n",
        "\n",
        "En fait, le paysage des coûts peut être assez complexe, comme le montrent les collines et les vallées de l'exemple ci-dessous. La méthode d'optimisation nous fait naviguer dans le paysage des coûts, à la recherche du minimum, comme le montrent les points et les lignes noirs. Nous pouvons constater que deux des trois recherches aboutissent à un minimum local du paysage, plutôt qu'à un minimum global.\n",
        "\n",
        "![Un collecteur courbé compliqué avec de nombreux pics et creux.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-loss-landscape.svg)\n",
        "\n",
        "Quel que soit le type de méthode d'optimisation utilisé, si le paysage des coûts est relativement plat, il peut être difficile pour la méthode de déterminer la direction appropriée de la recherche. Ce scénario est appelé <DefinitionTooltip definition=\"Lorsque les gradients des circuits quantiques paramétrés deviennent exponentiellement petits par rapport au nombre de qubits, l'optimisation devient difficile, voire impossible.\">plateau stérile,</DefinitionTooltip>, où le paysage des coûts devient progressivement plus plat (et donc plus difficile à déterminer pour atteindre le minimum). Pour un large éventail de circuits quantiques paramétrés, la probabilité que le gradient le long d'une direction raisonnable soit non nul avec une précision donnée diminue de manière exponentielle lorsque le nombre de qubits augmente.\n",
        "\n",
        "![Schéma d'un plateau géographique comparé à la pente d'une montagne, pour expliquer pourquoi une pente nous aide à trouver un minimum et qu'un plateau entrave nos efforts.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-barren-plateaus.svg)\n",
        "\n",
        "Bien que ce domaine fasse encore l'objet de recherches actives, nous avons quelques recommandations pour améliorer les performances d'optimisation :\n",
        "\n",
        "* Le **bootstrapping** peut aider la boucle d'optimisation à éviter de rester bloquée dans un espace de paramètres où le gradient est faible.\n",
        "* **Expérimentation d'un ansatz efficace sur le plan matériel** : étant donné que nous utilisons un système quantique bruyant comme oracle de boîte noire, la qualité de ces évaluations peut influer sur les performances de l'optimiseur. L'utilisation d'un ansatz efficace sur le plan matériel, tel que [`EfficientSU2`](/docs/api/qiskit/qiskit.circuit.library.EfficientSU2)peut éviter de produire des gradients exponentiellement petits.\n",
        "* **Expérimentation de la suppression et de l'atténuation des erreurs** : les primitives de la bibliothèque « IBM Quantum » offrent une interface simple permettant de tester différentes valeurs pour `optimization_level` et `resilience_setting`, respectivement. Cela peut réduire l'impact du bruit et rendre le processus d'optimisation plus efficace.\n",
        "* **Expérimentation avec des optimiseurs sans gradient** : Contrairement aux algorithmes d'optimisation basés sur le gradient, les optimiseurs tels que `COBYLA` ne s'appuient pas sur les informations de gradient pour optimiser les paramètres et sont donc moins susceptibles d'être affectés par le plateau stérile.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "182110bc-f952-4460-8048-d1df961ba860",
      "metadata": {},
      "source": [
        "<span id=\"summary\" />\n",
        "\n",
        "## Récapitulatif\n",
        "\n",
        "Cette leçon vous a appris à définir votre boucle d'optimisation :\n",
        "\n",
        "* Création d'une boucle d'optimisation\n",
        "* Comprendre les compromis lors de l'utilisation d'optimiseurs locaux et globaux\n",
        "* Explorer les plateaux stériles et comment les éviter\n",
        "\n",
        "Notre charge de travail variationnelle de haut niveau est terminée :\n",
        "\n",
        "![Un circuit quantique comportant à la fois une unité pour préparer l'état de référence et une seconde unité pour faire varier l'état à l'aide de paramètres variationnels.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-circuit.svg)\n",
        "\n",
        "Ensuite, nous explorerons des algorithmes variationnels spécifiques en gardant ce cadre à l'esprit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}