{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "da8ce695-8435-4099-b0fd-ade7b298f540",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Fonctions de coût\"\n",
        "description: \"Cette leçon explique ce qu'est une fonction de coût, comment elle est utilisée dans les algorithmes variationnels et en quoi elle peut différer de l'hamiltonien.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore resil nbsp eigenbasis expvals IIZZ IZIZ IZZI ZIIZ ZZII */}\n",
        "\n",
        "<span id=\"cost-functions\" />\n",
        "\n",
        "# Fonctions de coût\n",
        "\n",
        "Au cours de cette leçon, nous apprendrons à évaluer une *fonction de coût* :\n",
        "\n",
        "* Pour commencer, nous allons découvrir [les primitives d' IBM Quantum](/docs/guides/primitives)\n",
        "* Définir une *fonction de coût* $C(\\vec\\theta)$. Il s'agit d'une fonction spécifique au problème qui définit l'objectif du problème que l'optimiseur doit minimiser (ou maximiser)\n",
        "* Définition d'une stratégie de mesure à l'aide des primitives de l' IBM Quantum s afin d'optimiser le rapport vitesse/précision\n",
        "\n",
        " \n",
        "\n",
        "![Un diagramme montrant les composants clés d'une fonction de coût, y compris l'utilisation de primitives telles que l'estimateur et l'échantillonneur.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-workflow.svg)\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "4a9b79a9-c040-43d3-a4cf-314277e30eea",
      "metadata": {
        "gloss": {
          "primitives": {
            "text": "Basic and fundamental operations or data type. Qiskit has the Sampler and Estimator primitives to serve as building blocks to easily construct complex workloads, like variational algorithms.",
            "title": "Primitives"
          }
        }
      },
      "source": [
        "<span id=\"primitives\" />\n",
        "\n",
        "## Primitifs\n",
        "\n",
        "Tous les systèmes physiques, qu'ils soient classiques ou quantiques, peuvent exister dans différents états. Par exemple, une voiture sur une route peut avoir une certaine masse, position, vitesse ou accélération qui caractérisent son état. De même, les systèmes quantiques peuvent également avoir différentes configurations ou états, mais ils diffèrent des systèmes classiques par la façon dont nous traitons les mesures et l'évolution de l'état. Il en résulte des propriétés uniques, telles que la *superposition* et l' *enchevêtrement*, qui sont exclusives à la mécanique quantique. Tout comme nous pouvons décrire l'état d'une voiture à l'aide de propriétés physiques telles que la vitesse ou l'accélération, nous pouvons également décrire l'état d'un système quantique à l'aide d' *observables*, qui sont des objets mathématiques.\n",
        "\n",
        "En mécanique quantique, les états sont représentés par des vecteurs colonnes complexes normalisés, ou *kets* ( $|\\psi\\rangle$ ), et les observables sont des opérateurs linéaires hermitiens ( $\\hat{H}=\\hat{H}^{\\dagger}$ ) qui agissent sur les kets. Un vecteur propre ( $|\\lambda\\rangle$ ) d'une variable observable est appelé *état propre.* La mesure d'une observable pour l'un de ses états propres ( $|\\lambda\\rangle$ ) nous donnera la valeur propre correspondante ( $\\lambda$ ) comme résultat de lecture.\n",
        "\n",
        "Si vous vous demandez comment mesurer un système quantique et ce que vous pouvez mesurer, Qiskit propose deux sites <DefinitionTooltip definition=\"Opérations de base et fondamentales ou type de données. Qiskit dispose des primitives Sampler et Estimator qui servent de blocs de construction pour construire facilement des charges de travail algorithmiques complexes.\">Primitifs</DefinitionTooltip> qui peuvent vous aider :\n",
        "\n",
        "* `Sampler`: Étant donné un état quantique $|\\psi\\rangle$, cette primitive obtient la probabilité de chaque état de base de calcul possible.\n",
        "* `Estimator`: Étant donné une observable quantique $\\hat{H}$ et un état $|\\psi\\rangle$, cette primitive calcule la valeur attendue de $\\hat{H}$.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "00aa7dfa-34f1-440e-bbdb-19b6e6a1aff8",
      "metadata": {},
      "source": [
        "<span id=\"the-sampler-primitive\" />\n",
        "\n",
        "### La primitive Sampler\n",
        "\n",
        "La primitive `Sampler` calcule la probabilité d'obtenir chaque état possible $|k\\rangle$ à partir de la base de calcul, étant donné un circuit quantique qui prépare l'état $|\\psi\\rangle$. Elle calcule\n",
        "\n",
        "$$\n",
        "p_k = |\\langle k | \\psi \\rangle|^2 \\quad \\forall k \\in \\mathbb{Z}_2^n \\equiv \\{0,1,\\cdots,2^n-1\\},\n",
        "$$\n",
        "\n",
        "Où $n$ est le nombre de qubits et $k$ la représentation entière de toute chaîne binaire de sortie possible $\\{0,1\\}^n$ (c'est-à-dire les entiers de base $2$ ).\n",
        "\n",
        "L' IBM Quantum [`Sampler`](/docs/api/qiskit-ibm-runtime/sampler-v2) effectue plusieurs passages sur le circuit d'un dispositif quantique, en réalisant des mesures à chaque passage, puis en reconstituant la distribution de probabilité à partir des chaînes de bits récupérées. Plus il effectue d'itérations (ou *de calculs* ), plus les résultats seront précis, mais cela nécessite davantage de temps et de ressources quantiques.\n",
        "\n",
        "Toutefois, étant donné que le nombre de sorties possibles croît de manière exponentielle avec le nombre de qubits $n$ (c'est-à-dire $2^n$ ), le nombre de tirs devra également croître de manière exponentielle afin de capturer une distribution de probabilité *dense*. Par conséquent, `Sampler` n'est efficace que pour les distributions de probabilités *peu nombreuses*; l'état cible $|\\psi\\rangle$ doit pouvoir être exprimé sous la forme d'une combinaison linéaire des états de base de calcul, le nombre de termes augmentant au maximum de manière polynomiale avec le nombre de qubits :\n",
        "\n",
        "$$\n",
        "|\\psi\\rangle = \\sum^{\\text{Poly}(n)}_k w_k |k\\rangle.\n",
        "$$\n",
        "\n",
        "Le site `Sampler` peut également être configuré pour extraire les probabilités d'une sous-section du circuit, représentant un sous-ensemble de l'ensemble des états possibles.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "96a74b2b-6aa0-4ef1-a8d1-448b591429e4",
      "metadata": {
        "gloss": {
          "pauli": {
            "text": "Set of matrices commonly used in quantum computing to represent and manipulate quantum states, consisting of the identity matrix and the three Pauli matrices (X, Y, and Z).",
            "title": "Pauli Operators"
          }
        }
      },
      "source": [
        "<span id=\"the-estimator-primitive\" />\n",
        "\n",
        "### La primitive Estimator\n",
        "\n",
        "La primitive `Estimator` calcule la valeur d'espérance d'une observable $\\hat{H}$ pour un état quantique $|\\psi\\rangle$; où les probabilités de l'observable peuvent être exprimées comme $p_\\lambda = |\\langle\\lambda|\\psi\\rangle|^2$, étant $|\\lambda\\rangle$ les états propres de l'observable $\\hat{H}$. La valeur espérée est alors définie comme la moyenne de tous les résultats possibles $\\lambda$ (c'est-à-dire les valeurs propres de l'observable) d'une mesure de l'état $|\\psi\\rangle$, pondérée par les probabilités correspondantes :\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi := \\sum_\\lambda p_\\lambda \\lambda = \\langle \\psi | \\hat{H} | \\psi \\rangle\n",
        "$$\n",
        "\n",
        "Cependant, il n'est pas toujours possible de calculer la valeur attendue d'une observable, car on ignore souvent sa base propre. L' IBM Quantum utilise [`Estimator`](/docs/api/qiskit-ibm-runtime/estimator-v2) un processus algébrique complexe pour estimer la valeur attendue sur un dispositif quantique réel, en décomposant l'observable en une combinaison d'autres observables dont on connaît la base propre.\n",
        "\n",
        "En termes plus simples, `Estimator` décompose toute observable qu'il ne sait pas mesurer en observables plus simples et mesurables appelées <DefinitionTooltip definition=\"Ensemble de matrices couramment utilisées en informatique quantique pour représenter et manipuler les états quantiques, composé de la matrice d'identité et des trois matrices de Pauli (X, Y et Z).\">Opérateurs de Pauli</DefinitionTooltip>.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "6dcabf55-4462-4cf9-b6e4-e7860fd25b31",
      "metadata": {},
      "source": [
        "Tout opérateur peut être exprimé comme une combinaison de $4^n$ opérateurs de Pauli.\n",
        "\n",
        "$$\n",
        "\\hat{P}_k :=\n",
        "\\sigma_{k_{n-1}}\\otimes \\cdots \\otimes \\sigma_{k_0} \\quad\n",
        "\\forall k \\in \\mathbb{Z}_4^n \\equiv \\{0,1,\\cdots,4^n-1\\}, \\\\\n",
        "$$\n",
        "\n",
        "such that\n",
        "\n",
        "$$\n",
        "\\hat{H} = \\sum^{4^n-1}_{k=0} w_k \\hat{P}_k\n",
        "$$\n",
        "\n",
        "où $n$ est le nombre de qubits, $k \\equiv k_{n-1} \\cdots k_0$ pour $k_l \\in \\mathbb{Z}_4 \\equiv \\{0, 1, 2, 3\\}$ (c'est-à-dire les entiers de base $4$ ), et $(\\sigma_0, \\sigma_1, \\sigma_2, \\sigma_3) := (I, X, Y, Z)$.\n",
        "\n",
        "Après avoir effectué cette décomposition, `Estimator` dérive un nouveau circuit $V_k|\\psi\\rangle$ pour chaque observable $\\hat{P}_k$ (du circuit original), afin de *diagonaliser* efficacement l'observable de Pauli dans la base de calcul et de la mesurer. Nous pouvons facilement mesurer les observables de Pauli parce que nous connaissons $V_k$ à l'avance, ce qui n'est généralement pas le cas pour les autres observables.\n",
        "\n",
        "Pour chaque $\\hat{P}_{k}$, le `Estimator` exécute plusieurs fois le circuit correspondant sur un dispositif quantique, mesure l'état de sortie dans la base de calcul et calcule la probabilité $p_{kj}$ d'obtenir chaque sortie possible $j$. Il recherche ensuite la valeur propre $\\lambda_{kj}$ de $P_k$ correspondant à chaque sortie $j$, la multiplie par $w_k$ et additionne tous les résultats pour obtenir la valeur attendue de l'observable $\\hat{H}$ pour l'état donné $|\\psi\\rangle$.\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi =\n",
        "\\sum_{k=0}^{4^n-1} w_k \\sum_{j=0}^{2^n-1}p_{kj} \\lambda_{kj},\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "dcffb899-ef2d-4dca-b2a7-11c197d45ed0",
      "metadata": {
        "gloss": {
          "sampling": {
            "text": "The process of taking several measurements of one or several things.",
            "title": "Sampling"
          }
        }
      },
      "source": [
        "Étant donné que le calcul de la valeur espérée de $4^n$ Paulis n'est pas pratique (c'est-à-dire qu'il croît de manière exponentielle), `Estimator` ne peut être efficace que lorsqu'un grand nombre de $w_k$ sont nuls (c'est-à-dire une décomposition de Pauli *éparse* au lieu d'être *dense* ). Formellement, nous disons que, pour que ce calcul puisse *être résolu efficacement*, le nombre de termes non nuls doit croître au maximum de façon polynomiale avec le nombre de qubits $n$ : $\\hat{H} = \\sum^{\\text{Poly}(n)}_k w_k \\hat{P}_k.$\n",
        "\n",
        "Le lecteur peut remarquer l'hypothèse implicite selon laquelle la probabilité <DefinitionTooltip definition=\"Processus consistant à prendre plusieurs mesures d'une ou de plusieurs choses.\">échantillonnage</DefinitionTooltip> doit également être efficace, comme expliqué pour `Sampler`, ce qui signifie que\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi =\n",
        "\\sum_{k}^{\\text{Poly}(n)} w_k \\sum_{j}^{\\text{Poly}(n)}p_{kj} \\lambda_{kj}.\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "c1547f3e-3c05-46d9-be5a-3e7e4141dc92",
      "metadata": {},
      "source": [
        "<span id=\"guided-example-to-calculate-expectation-values\" />\n",
        "\n",
        "### Exemple guidé pour calculer les valeurs attendues\n",
        "\n",
        "Supposons l'état d'un qubit unique $|+\\rangle := H|0\\rangle = \\frac{1}{\\sqrt{2}}(|0\\rangle + |1\\rangle)$, et l'observable\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\hat{H}\n",
        "& = \\begin{pmatrix}\n",
        "-1 & 2 \\\\\n",
        "2 & 1 \\\\\n",
        "\\end{pmatrix}\\\\[1mm]\n",
        "& = 2X - Z\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "avec la valeur théorique d'espérance suivante $\\langle\\hat{H}\\rangle_+ = \\langle+|\\hat{H}|+\\rangle = 2.$\n",
        "\n",
        "Comme nous ne savons pas comment mesurer cette observable, nous ne pouvons pas calculer directement sa valeur d'espérance et nous devons la réexprimer sous la forme $\\langle\\hat{H}\\rangle_+ = 2\\langle X \\rangle_+ - \\langle Z \\rangle_+ $. On peut montrer que l'on obtient le même résultat en notant que $\\langle+|X|+\\rangle = 1$, et $\\langle+|Z|+\\rangle = 0$.\n",
        "\n",
        "Voyons comment calculer directement $\\langle X \\rangle_+$ et $\\langle Z \\rangle_+$. Comme $X$ et $Z$ ne commutent pas (c'est-à-dire qu'ils ne partagent pas la même base propre), ils ne peuvent pas être mesurés simultanément, c'est pourquoi nous avons besoin des circuits auxiliaires :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "7c32e970-f9cb-48ec-a60b-2dccb0744141",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/7c32e970-f9cb-48ec-a60b-2dccb0744141-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",
        "\n",
        "# The following code will work for any other initial single-qubit state and observable\n",
        "original_circuit = QuantumCircuit(1)\n",
        "original_circuit.h(0)\n",
        "\n",
        "H = SparsePauliOp([\"X\", \"Z\"], [2, -1])\n",
        "\n",
        "aux_circuits = []\n",
        "for pauli in H.paulis:\n",
        "    aux_circ = original_circuit.copy()\n",
        "    aux_circ.barrier()\n",
        "    if str(pauli) == \"X\":\n",
        "        aux_circ.h(0)\n",
        "    elif str(pauli) == \"Y\":\n",
        "        aux_circ.sdg(0)\n",
        "        aux_circ.h(0)\n",
        "    else:\n",
        "        aux_circ.id(0)\n",
        "    aux_circ.measure_all()\n",
        "    aux_circuits.append(aux_circ)\n",
        "\n",
        "original_circuit.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "ec3cf7af-66b2-4ea9-94d7-6ec6ba2bfc5e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/ec3cf7af-66b2-4ea9-94d7-6ec6ba2bfc5e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Auxiliary circuit for X\n",
        "aux_circuits[0].draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "678d3bd0-d8fd-4767-b0a6-77cae945810a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/678d3bd0-d8fd-4767-b0a6-77cae945810a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Auxiliary circuit for Z\n",
        "aux_circuits[1].draw(\"mpl\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "beef97f0-c968-4a3f-9b7b-fed39b88cbe3",
      "metadata": {},
      "source": [
        "Nous pouvons maintenant effectuer le calcul manuellement à l'aide de `Sampler` et vérifier les résultats sur `Estimator`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d59dffe0-85cb-4849-8421-03bbcc9668df",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Sampler results:\n",
            "  >> Expected value of X: 1.00000\n",
            "  >> Expected value of Z: 0.00420\n",
            "  >> Total expected value: 1.99580\n",
            "Estimator results:\n",
            "  >> Expected value of X: 1.00000\n",
            "  >> Expected value of Z: 0.00000\n",
            "  >> Total expected value: 2.00000\n"
          ]
        }
      ],
      "source": [
        "from qiskit.primitives import StatevectorSampler, StatevectorEstimator\n",
        "from qiskit.result import QuasiDistribution\n",
        "import numpy as np\n",
        "\n",
        "\n",
        "## SAMPLER\n",
        "shots = 10000\n",
        "sampler = StatevectorSampler()\n",
        "job = sampler.run(aux_circuits, shots=shots)\n",
        "\n",
        "# Run the sampler job and step through results\n",
        "expvals = []\n",
        "for index, pauli in enumerate(H.paulis):\n",
        "    data_pub = job.result()[index].data\n",
        "    bitstrings = data_pub.meas.get_bitstrings()\n",
        "    counts = data_pub.meas.get_counts()\n",
        "    quasi_dist = QuasiDistribution(\n",
        "        {outcome: freq / shots for outcome, freq in counts.items()}\n",
        "    )\n",
        "\n",
        "    # Use the probabilities and known eigenvalues of Pauli operators to estimate\n",
        "    # the expectation value.\n",
        "    val = 0\n",
        "\n",
        "    if str(pauli) == \"X\":\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "\n",
        "    if str(pauli) == \"Y\":\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "\n",
        "    if str(pauli) == \"Z\":\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "\n",
        "    expvals.append(val)\n",
        "\n",
        "# Print expectation values\n",
        "\n",
        "print(\"Sampler results:\")\n",
        "for pauli, expval in zip(H.paulis, expvals):\n",
        "    print(f\"  >> Expected value of {str(pauli)}: {expval:.5f}\")\n",
        "\n",
        "total_expval = np.sum(H.coeffs * expvals).real\n",
        "print(f\"  >> Total expected value: {total_expval:.5f}\")\n",
        "\n",
        "# Use estimator for comparison\n",
        "observables = [\n",
        "    *H.paulis,\n",
        "    H,\n",
        "]  # Note: run for individual Paulis as well as full observable H\n",
        "\n",
        "estimator = StatevectorEstimator()\n",
        "job = estimator.run([(original_circuit, observables)])\n",
        "estimator_expvals = job.result()[0].data.evs\n",
        "\n",
        "# Print results\n",
        "print(\"Estimator results:\")\n",
        "for obs, expval in zip(observables, estimator_expvals):\n",
        "    if obs is not H:\n",
        "        print(f\"  >> Expected value of {str(obs)}: {expval:.5f}\")\n",
        "    else:\n",
        "        print(f\"  >> Total expected value: {expval:.5f}\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "44f13c37-50e3-426c-90cb-f730a81e20d1",
      "metadata": {},
      "source": [
        "<span id=\"mathematical-rigor-optional\" />\n",
        "\n",
        "### Rigueur mathématique (facultatif)\n",
        "\n",
        "En exprimant $|\\psi\\rangle$ par rapport à la base des états propres de $\\hat{H}$, $|\\psi\\rangle = \\sum_\\lambda a_\\lambda |\\lambda\\rangle$, on obtient ce qui suit :\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle \\psi | \\hat{H} | \\psi \\rangle\n",
        "& = \\bigg(\\sum_{\\lambda'}a^*_{\\lambda'} \\langle \\lambda'|\\bigg) \\hat{H}\n",
        "  \\bigg(\\sum_{\\lambda} a_\\lambda | \\lambda\\rangle\\bigg)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda}\n",
        "  \\langle \\lambda'|\\hat{H}| \\lambda\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda} \\lambda\n",
        "\\langle \\lambda'| \\lambda\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda} \\lambda\n",
        "\\cdot \\delta_{\\lambda, \\lambda'}\\\\[1mm]\n",
        "\n",
        "& = \\sum_\\lambda |a_\\lambda|^2 \\lambda\\\\[1mm]\n",
        "\n",
        "& = \\sum_\\lambda p_\\lambda \\lambda\\\\[1mm]\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "6a3bea3d-a2f6-4033-85f9-9a10c5d5c64c",
      "metadata": {
        "gloss": {
          "hermitian": {
            "text": "A hermitian is a square matrix that is equal to its own conjugate transpose, or a linear operator that is self-adjoint.",
            "title": "Hermitian"
          }
        }
      },
      "source": [
        "Comme nous ne connaissons pas les valeurs propres ou les états propres de l'observable cible $\\hat{H}$, nous devons d'abord examiner sa diagonalisation. Étant donné que $\\hat{H}$ est <DefinitionTooltip definition=\"Un hermitien est une matrice carrée qui est égale à sa propre transposition conjuguée, ou un opérateur linéaire qui est auto-adjoint.\">Hermitien</DefinitionTooltip>, il existe une transformation unitaire $V$ telle que $\\hat{H}=V^\\dagger \\Lambda V,$ où $\\Lambda$ est la matrice diagonale des valeurs propres, donc $\\langle j | \\Lambda | k \\rangle = 0$ si $j\\neq k$, et $\\langle j | \\Lambda | j \\rangle = \\lambda_j$.\n",
        "\n",
        "Cela implique que la valeur attendue peut être réécrite comme suit :\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle\n",
        "& = \\langle\\psi|V^\\dagger \\Lambda V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\langle\\psi|V^\\dagger \\bigg(\\sum_{j=0}^{2^n-1} |j\\rangle\n",
        "\\langle j|\\bigg) \\Lambda \\bigg(\\sum_{k=0}^{2^n-1} |k\\rangle \\langle k|\\bigg) V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1} \\sum_{k=0}^{2^n-1}\\langle\\psi|V^\\dagger |j\\rangle\n",
        "\\langle j| \\Lambda  |k\\rangle \\langle k| V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1}\\langle\\psi|V^\\dagger |j\\rangle\n",
        "\\langle j| \\Lambda  |j\\rangle \\langle j| V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1}|\\langle j| V|\\psi\\rangle|^2 \\lambda_j\\\\[1mm]\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Étant donné que si un système est dans l'état $|\\phi\\rangle = V |\\psi\\rangle$, la probabilité de mesurer $| j\\rangle$ est $p_j = |\\langle j|\\phi \\rangle|^2$, la valeur attendue ci-dessus peut être exprimée comme suit :\n",
        "\n",
        "$$\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle =\n",
        "\\sum_{j=0}^{2^n-1} p_j \\lambda_j.\n",
        "$$\n",
        "\n",
        "Il est très important de noter que les probabilités sont tirées de l'état $V |\\psi\\rangle$ au lieu de $|\\psi\\rangle$. C'est pourquoi la matrice $V$ est absolument nécessaire.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "c21a9a05-6e07-45dc-a090-4414dfdf2521",
      "metadata": {},
      "source": [
        "Vous vous demandez peut-être comment obtenir la matrice $V$ et les valeurs propres $\\Lambda$. Si vous aviez déjà les valeurs propres, il ne serait pas nécessaire d'utiliser un ordinateur quantique puisque le but des algorithmes variationnels est de trouver ces valeurs propres de $\\hat{H}$.\n",
        "\n",
        "Heureusement, il existe un moyen de contourner cela : toute matrice de type « $2^n \\times 2^n$ » peut s’écrire sous la forme d’une combinaison linéaire de produits tensoriels de type « $4^n$ » entre des matrices de Pauli de type « $n$ » et des identités, qui sont toutes à la fois hermitiennes et unitaires, avec des valeurs connues pour $V$ et $\\Lambda$. C’est ce que fait `Estimator` en interne l’ IBM Quantum en décomposant tout objet [`Operator`](/docs/api/qiskit/qiskit.quantum_info.Operator) en un [`SparsePauliOp`](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp).\n",
        "\n",
        "Voici les opérateurs qui peuvent être utilisés :\n",
        "\n",
        "$$\n",
        "\\begin{array}{c|c|c|c}\n",
        "  \\text{Operator} & \\sigma & V & \\Lambda \\\\[1mm]\n",
        "  \\hline\n",
        "  I & \\sigma_0 = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} & V_0 = I & \\Lambda_0 = I = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  X & \\sigma_1 = \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\end{pmatrix} & V_1 = H =\\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 1 \\\\ 1 & -1 \\end{pmatrix} & \\Lambda_1 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  Y & \\sigma_2 = \\begin{pmatrix} 0 & -i \\\\ i & 0 \\end{pmatrix} & V_2 = HS^\\dagger  =\\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 1 \\\\ 1 & -1 \\end{pmatrix}\\cdot  \\begin{pmatrix} 1 & 0 \\\\ 0 & -i \\end{pmatrix} = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & -i \\\\ 1 & i \\end{pmatrix}\\quad & \\Lambda_2 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  Z & \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} & V_3 = I & \\Lambda_3 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix}\n",
        "\\end{array}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "9e1c0933-5fbe-466d-9114-92d66d21785f",
      "metadata": {},
      "source": [
        "Réécrivons donc $\\hat{H}$ en respectant les Paulis et les identités :\n",
        "\n",
        "$$\n",
        "\\hat{H} =\n",
        "\\sum_{k_{n-1}=0}^3...\n",
        "\\sum_{k_0=0}^3 w_{k_{n-1}...k_0}\n",
        "\\sigma_{k_{n-1}}\\otimes ... \\otimes \\sigma_{k_0} = \\sum_{k=0}^{4^n-1} w_k \\hat{P}_k,\n",
        "$$\n",
        "\n",
        "où $k = \\sum_{l=0}^{n-1} 4^l k_l \\equiv k_{n-1}...k_0$ pour $k_{n-1},...,k_0\\in \\{0,1,2,3\\}$ (c'est-à-dire la base $4$ ), et $\\hat{P}_{k} := \\sigma_{k_{n-1}}\\otimes ... \\otimes \\sigma_{k_0}$ :\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle\n",
        "& = \\sum_{k=0}^{4^n-1} w_k\n",
        "\\sum_{j=0}^{2^n-1}|\\langle j| V_k|\\psi\\rangle|^2 \\langle j| \\Lambda_k |j\\rangle \\\\[1mm]\n",
        "\n",
        "& = \\sum_{k=0}^{4^n-1} w_k \\sum_{j=0}^{2^n-1}p_{kj} \\lambda_{kj}, \\\\[1mm]\n",
        "\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "où $V_k := V_{k_{n-1}}\\otimes ... \\otimes V_{k_0}$ et $\\Lambda_k := \\Lambda_{k_{n-1}}\\otimes ... \\otimes \\Lambda_{k_0}$, tels que : $\\hat{P_k}=V_k^\\dagger \\Lambda_k V_k.$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "42f771a0-486d-4a5f-9df1-a1720d7bf252",
      "metadata": {
        "gloss": {
          "oracle": {
            "text": " A hypothetical device or software component that performs a specific function, but the internal workings of which are unknown. A user is only aware of the inputs and outputs of the black box, and has no knowledge of how the black box processes the inputs to produce the outputs.",
            "title": "Black-box oracle"
          }
        }
      },
      "source": [
        "<span id=\"cost-functions\" />\n",
        "\n",
        "## Fonctions de coût\n",
        "\n",
        "En général, les fonctions de coût sont utilisées pour décrire l'objectif d'un problème et la performance d'un état d'essai par rapport à cet objectif. Cette définition peut être appliquée à divers exemples en chimie, en apprentissage automatique, en finance, en optimisation, etc.\n",
        "\n",
        "Prenons un exemple simple de recherche de l'état fondamental d'un système. Notre objectif est de minimiser la valeur espérée de l'observable représentant l'énergie (hamiltonien $\\hat{\\mathcal{H}}$ ) :\n",
        "\n",
        "$$\n",
        "\\min_{\\vec\\theta} \\langle\\psi(\\vec\\theta)|\\hat{\\mathcal{H}}|\\psi(\\vec\\theta)\\rangle\n",
        "$$\n",
        "\n",
        "Nous pouvons utiliser le site `Estimator` pour évaluer la valeur de l'espérance et transmettre cette valeur à un optimiseur pour qu'il la minimise. Si l'optimisation est réussie, elle renvoie un ensemble de valeurs optimales des paramètres $\\vec\\theta^*$, à partir desquelles nous pourrons construire l'état de la solution proposée $|\\psi(\\vec\\theta^*)\\rangle$ et calculer la valeur d'espérance observée $C(\\vec\\theta^*)$.\n",
        "\n",
        "Remarquez que nous ne pourrons minimiser la fonction de coût que pour l'ensemble limité d'états que nous considérons. Cela nous amène à deux possibilités distinctes :\n",
        "\n",
        "* **Notre ansatz ne définit pas l'état de la solution dans l'espace de recherche** : Si c'est le cas, notre optimiseur ne trouvera jamais la solution et nous devons expérimenter d'autres ansatz susceptibles de représenter notre espace de recherche de manière plus précise.\n",
        "* **Notre optimiseur est incapable de trouver cette solution valide** : L'optimisation peut être définie globalement et localement. Nous verrons ce que cela signifie dans la section suivante.\n",
        "\n",
        "Dans l'ensemble, nous effectuerons une boucle d'optimisation classique, mais en confiant l'évaluation de la fonction de coût à un ordinateur quantique. De ce point de vue, on peut considérer l'optimisation comme une entreprise purement classique dans laquelle nous appelons une adresse <DefinitionTooltip definition=\"Un dispositif hypothétique ou un composant logiciel qui remplit une fonction spécifique, mais dont le fonctionnement interne est inconnu. L'utilisateur ne connaît que les entrées et les sorties de la boîte noire et ne sait pas comment la boîte noire traite les entrées pour produire les sorties.\">oracle quantique à boîte noire</DefinitionTooltip> chaque fois que l'optimiseur doit évaluer la fonction de coût.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "650faaab-b964-4587-a39f-d6cc80b225b2",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func_vqe(params, circuit, 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 = (circuit, hamiltonian, params)\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a01a54f2-da16-4008-b422-fa7ded531f67",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/a01a54f2-da16-4008-b422-fa7ded531f67-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit.library import TwoLocal\n",
        "\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",
        "theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()\n",
        "ansatz.decompose().draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7357e05e-f68a-4cd5-bf0d-77eab963a86d",
      "metadata": {},
      "source": [
        "Nous commencerons par utiliser un simulateur : le site StatevectorEstimator. C'est généralement conseillé pour le débogage, mais nous ferons immédiatement suivre l'exécution du débogage d'un calcul sur du matériel quantique réel. De plus en plus, les problèmes d'intérêt ne peuvent plus être simulés de manière classique sans les installations de supercalculateurs les plus modernes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "883bcb28-15d0-4046-a654-f43d9480a642",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[-0.58744589]\n"
          ]
        }
      ],
      "source": [
        "estimator = StatevectorEstimator()\n",
        "cost = cost_func_vqe(theta_list, ansatz, observable, estimator)\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1a94eba3-146d-4eee-970d-9a966d1e15f3",
      "metadata": {},
      "source": [
        "Nous allons maintenant passer à l'exécution sur un véritable ordinateur quantique. Notez les modifications apportées à la syntaxe. Les étapes impliquant le pass\\_manager seront abordées plus en détail dans l'exemple suivant. L'utilisation d'une session du service de calcul « IBM Quantum » constitue une étape particulièrement importante dans les algorithmes variationnels. Le lancement d'une session vous permet d'exécuter plusieurs itérations d'un algorithme variationnel sans avoir à attendre dans une nouvelle file d'attente à chaque mise à jour des paramètres. C'est important lorsque les temps d'attente sont longs et/ou que de nombreuses itérations sont nécessaires. Veuillez noter que les utilisateurs de la formule Open Plan ne peuvent pas utiliser les sessions Quantum Compute. Si vous n'avez pas accès aux sessions, vous pouvez réduire le nombre d'itérations que vous soumettez à un moment donné et enregistrer les paramètres les plus récents afin de les réutiliser lors de futurs calculs. Si vous soumettez un nombre trop élevé d'itérations ou si les temps d'attente sont trop longs, vous risquez d'obtenir le code d'erreur 1217, qui correspond à des délais trop importants entre les soumissions de tâches.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e4ca4722-46ab-418f-8a1f-740a65e92eb9",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Estimated usage: < 1 min. Benchmarked at 7 seconds on an Eagle processor\n",
        "# Load necessary packages:\n",
        "\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    Session,\n",
        "    EstimatorOptions,\n",
        "    EstimatorV2 as Estimator,\n",
        ")\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "# Select the least busy backend:\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        ")\n",
        "# Or get a specific backend:\n",
        "# backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Use a pass manager to transpile the circuit and observable for the specific backend being used:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "isa_ansatz = pm.run(ansatz)\n",
        "isa_observable = observable.apply_layout(layout=isa_ansatz.layout)\n",
        "\n",
        "\n",
        "# Set estimator options\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "# Open a Quantum Compute session:\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "    cost = cost_func_vqe(theta_list, isa_ansatz, isa_observable, estimator)\n",
        "\n",
        "session.close()\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "95dc0e83-46ce-4839-91c3-af550aef9ddf",
      "metadata": {},
      "source": [
        "Il est à noter que les valeurs obtenues à partir des deux calculs ci-dessus sont très similaires. Les techniques permettant d'améliorer les résultats seront examinées plus loin.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7f3951a3-47ad-4ddb-90a0-2218b1e11d15",
      "metadata": {},
      "source": [
        "<span id=\"example-mapping-to-non-physical-systems\" />\n",
        "\n",
        "### Exemple de mise en correspondance avec des systèmes non physiques\n",
        "\n",
        "Le problème de la coupe maximale (max-cut) est un problème d'optimisation combinatoire qui consiste à diviser les sommets d'un graphe en deux ensembles disjoints de manière à maximiser le nombre d'arêtes entre ces deux ensembles. Plus formellement, pour un graphe non orienté $G=(V,E)$, où $V$ est l'ensemble des sommets et $E$ l'ensemble des arêtes, le problème de la coupe maximale consiste à partitionner les sommets en deux sous-ensembles disjoints, $S$ et $T$, de telle sorte que le nombre d'arêtes dont une extrémité appartient à $S$ et l'autre à $T$ soit maximisé.\n",
        "\n",
        "On peut appliquer l'algorithme de max-cut pour résoudre divers problèmes, notamment : le regroupement, la conception de réseaux, les transitions de phase, etc. Nous allons commencer par créer un graphe du problème :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "739ca7a0-fae3-4389-a8de-7ae93c13b9e1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/739ca7a0-fae3-4389-a8de-7ae93c13b9e1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import rustworkx as rx\n",
        "from rustworkx.visualization import mpl_draw\n",
        "\n",
        "n = 4\n",
        "G = rx.PyGraph()\n",
        "G.add_nodes_from(range(n))\n",
        "# The edge syntax is (start, end, weight)\n",
        "edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]\n",
        "G.add_edges_from(edges)\n",
        "\n",
        "mpl_draw(\n",
        "    G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color=\"#1192E8\"\n",
        ")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "3246e173-b1a8-48c2-bce8-f80a1524bcfb",
      "metadata": {},
      "source": [
        "Ce problème peut être exprimé comme un problème d'optimisation binaire. Pour chaque nœud $0 \\leq i < n$, où $n$ est le nombre de nœuds du graphe (dans ce cas $n=4$ ), nous considérerons la variable binaire $x_i$. Cette variable aura la valeur $1$ si le nœud $i$ fait partie de l'un des groupes que nous appellerons $1$ et $0$ s'il fait partie de l'autre groupe, que nous appellerons $0$. Nous désignerons également par $w_{ij}$ (élément $(i,j)$ de la matrice d'adjacence $w$ ) le poids de l'arête qui va du nœud $i$ au nœud $j$. Comme le graphe n'est pas orienté, $w_{ij}=w_{ji}$. Nous pouvons donc formuler notre problème comme étant la maximisation de la fonction de coût suivante :\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "C(\\vec{x})\n",
        "& =\\sum_{i,j=0}^n w_{ij} x_i(1-x_j)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n w_{ij} x_i - \\sum_{i,j=0}^n w_{ij} x_ix_j\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n w_{ij} x_i - \\sum_{i=0}^n \\sum_{j=0}^i 2w_{ij} x_ix_j\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Pour résoudre ce problème avec un ordinateur quantique, nous allons exprimer la fonction de coût comme la valeur attendue d'une observable. Cependant, les observables que Qiskit admet consistent nativement en des opérateurs de Pauli, qui ont des valeurs propres $1$ et $-1$ au lieu de $0$ et $1$. C'est pourquoi nous allons effectuer le changement de variable suivant :\n",
        "\n",
        "Où $\\vec{x}=(x_0,x_1,\\cdots ,x_{n-1})$. Nous pouvons utiliser la matrice d'adjacence $w$ pour accéder facilement aux poids de toutes les arêtes. Ce résultat sera utilisé pour obtenir notre fonction de coût :\n",
        "\n",
        "$$\n",
        "z_i = 1-2x_i \\rightarrow x_i = \\frac{1-z_i}{2}\n",
        "$$\n",
        "\n",
        "Cela implique que :\n",
        "\n",
        "$$\n",
        "\\begin{array}{lcl} x_i=0 & \\rightarrow & z_i=1 \\\\ x_i=1 & \\rightarrow & z_i=-1.\\end{array}\n",
        "$$\n",
        "\n",
        "La nouvelle fonction de coût que nous voulons maximiser est donc la suivante :\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "C(\\vec{z})\n",
        "& = \\sum_{i,j=0}^n w_{ij} \\bigg(\\frac{1-z_i}{2}\\bigg)\\bigg(1-\\frac{1-z_j}{2}\\bigg)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n \\frac{w_{ij}}{4} - \\sum_{i,j=0}^n \\frac{w_{ij}}{4} z_iz_j\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} -  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} z_iz_j\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "De plus, la tendance naturelle d'un ordinateur quantique est de trouver des minima (généralement l'énergie la plus basse) plutôt que des maxima. Ainsi, au lieu de maximiser $C(\\vec{z})$, nous allons minimiser :\n",
        "\n",
        "$$\n",
        "-C(\\vec{z}) =  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} z_iz_j -  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2}\n",
        "$$\n",
        "\n",
        "Maintenant que nous avons une fonction de coût à minimiser dont les variables peuvent prendre les valeurs $-1$ et $1$, nous pouvons faire l'analogie suivante avec la fonction de Pauli $Z$ :\n",
        "\n",
        "$$\n",
        "z_i \\equiv Z_i = \\overbrace{I}^{n-1}\\otimes ... \\otimes \\overbrace{Z}^{i} \\otimes ... \\otimes \\overbrace{I}^{0}\n",
        "$$\n",
        "\n",
        "En d'autres termes, la variable $z_i$ sera équivalente à une porte $Z$ agissant sur le qubit $i$. En outre :\n",
        "\n",
        "$$\n",
        "Z_i|x_{n-1}\\cdots x_0\\rangle = z_i|x_{n-1}\\cdots x_0\\rangle \\rightarrow \\langle x_{n-1}\\cdots x_0 |Z_i|x_{n-1}\\cdots x_0\\rangle = z_i\n",
        "$$\n",
        "\n",
        "L'observable que nous allons considérer est alors :\n",
        "\n",
        "$$\n",
        "\\hat{H} = \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} Z_iZ_j\n",
        "$$\n",
        "\n",
        "à laquelle il faudra ensuite ajouter le terme indépendant :\n",
        "\n",
        "$$\n",
        "\\texttt{offset} = - \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "83c86ba1-8b9d-4222-bb7e-637e6f29e5cd",
      "metadata": {},
      "source": [
        "L'opérateur est une combinaison linéaire de termes avec des opérateurs Z sur des nœuds reliés par une arête (rappelons que le 0e qubit est le plus à droite) : $IIZZ + IZIZ + IZZI + ZIIZ + ZZII$. Une fois l'opérateur construit, l'ansatz pour l'algorithme QAOA peut facilement être construit en utilisant le circuit `QAOAAnsatz` de la bibliothèque de circuits Qiskit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "29398315-3baf-4363-88fc-69b6438e4afa",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/29398315-3baf-4363-88fc-69b6438e4afa-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 17,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit.library import QAOAAnsatz\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "hamiltonian = SparsePauliOp.from_list(\n",
        "    [(\"IIZZ\", 1), (\"IZIZ\", 1), (\"IZZI\", 1), (\"ZIIZ\", 1), (\"ZZII\", 1)]\n",
        ")\n",
        "\n",
        "\n",
        "ansatz = QAOAAnsatz(hamiltonian, reps=2)\n",
        "# Draw\n",
        "ansatz.decompose(reps=3).draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "91d02379-9da6-426b-a5f0-d1fb75a891ee",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Offset: -2.5\n"
          ]
        }
      ],
      "source": [
        "# Sum the weights, and divide by 2\n",
        "\n",
        "offset = -sum(edge[2] for edge in edges) / 2\n",
        "print(f\"\"\"Offset: {offset}\"\"\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "84a98a08-00e4-4b05-a3f0-de29a7e73a60",
      "metadata": {},
      "source": [
        "L'estimateur « IBM Quantum » prenant directement en entrée un hamiltonien et une approximation paramétrée, et renvoyant l'énergie nécessaire, la fonction de coût pour une instance QAOA est assez simple :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "73ac80dd-87cf-4a43-b19b-e89a4782a44a",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func(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",
        "    #    cost = estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0]\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "fc6af87b-a838-43b8-96ce-0af790fe0172",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.473098768180865\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "\n",
        "x0 = 2 * np.pi * np.random.rand(ansatz.num_parameters)\n",
        "\n",
        "estimator = StatevectorEstimator()\n",
        "cost = cost_func_vqe(x0, ansatz, hamiltonian, estimator)\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "389b7e86-cd61-4184-96c3-ae91b8ad59f5",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.1120776913677988\n"
          ]
        }
      ],
      "source": [
        "# Estimated usage: < 1 min, benchmarked at 6 seconds on ibm_osaka, 5-23-24\n",
        "# Load some necessary packages:\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator\n",
        "\n",
        "# Select the least busy backend:\n",
        "\n",
        "backend = service.least_busy(\n",
        "    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        ")\n",
        "\n",
        "# Or get a specific backend:\n",
        "# backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Use a pass manager to transpile the circuit and observable for the specific backend being used:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "isa_ansatz = pm.run(ansatz)\n",
        "isa_hamiltonian = hamiltonian.apply_layout(layout=isa_ansatz.layout)\n",
        "\n",
        "# Set estimator options\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "# Open a Quantum Compute session:\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "    cost = cost_func_vqe(x0, isa_ansatz, isa_hamiltonian, estimator)\n",
        "\n",
        "# Close session after done\n",
        "session.close()\n",
        "print(cost)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "16c94186-366c-4ac4-8745-4e7da283f8da",
      "metadata": {},
      "source": [
        "Nous reviendrons sur cet exemple dans les applications afin d'étudier comment tirer parti d'un optimiseur pour itérer dans l'espace de recherche. D'une manière générale, il s'agit des éléments suivants\n",
        "\n",
        "* Utilisation d'un optimiseur pour trouver les paramètres optimaux\n",
        "* Lier les paramètres optimaux à l'ansatz pour trouver les valeurs propres\n",
        "* Traduire les valeurs propres dans notre définition du problème\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "d14d5800-24c1-4e4b-b692-9bdb9ef4440f",
      "metadata": {},
      "source": [
        "<span id=\"measurement-strategy-speed-versus-accuracy\" />\n",
        "\n",
        "## Stratégie de mesure : vitesse ou précision?\n",
        "\n",
        "Comme nous l'avons mentionné, nous utilisons un ordinateur quantique bruyant comme *oracle de boîte noire*, où le bruit peut rendre les valeurs récupérées non déterministes, entraînant des fluctuations aléatoires qui, à leur tour, nuiront - ou même empêcheront complètement - la convergence de certains optimiseurs vers une solution proposée. Il s'agit d'un problème général que nous devons résoudre au fur et à mesure que nous explorons l'utilité quantique et que nous progressons vers un avantage quantique :\n",
        "\n",
        "![Graphique montrant comment le coût de la simulation varie en fonction de la complexité du circuit. Avec un ordinateur classique, elle croît de manière exponentielle. Avec l'atténuation des erreurs quantiques, il devrait y avoir un seuil à partir duquel cela devient avantageux. La correction quantique des erreurs permet une croissance linéaire du coût de la simulation et sera certainement avantageuse.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-path-to-quantum-advantage.svg)\n",
        "\n",
        "Nous pouvons utiliser les options de suppression et d'atténuation des erreurs des primitives « IBM Quantum » pour lutter contre le bruit et optimiser l'utilité des ordinateurs quantiques actuels.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "f23f0d82-d4f9-4485-aad3-cd2f8fd19b4f",
      "metadata": {
        "gloss": {
          "overhead": {
            "text": "Extra costs introduced by new techniques, relative to a base implementation.",
            "title": "Overhead"
          }
        }
      },
      "source": [
        "<span id=\"error-suppression\" />\n",
        "\n",
        "### Suppression des erreurs\n",
        "\n",
        "[La suppression d'erreurs](/docs/guides/error-mitigation-and-suppression-techniques) désigne les techniques utilisées pour optimiser et transformer un circuit lors de la compilation afin de réduire au minimum les erreurs. Il s'agit d'une technique de gestion des erreurs élémentaire qui entraîne généralement une légère augmentation de l' <DefinitionTooltip definition=\"Coûts supplémentaires introduits par de nouvelles techniques, par rapport à une mise en œuvre de base.\">temps système</DefinitionTooltip> e de prétraitement par rapport à la durée d'exécution totale. La surcharge implique la transcompilation des circuits pour qu'ils puissent s'exécuter sur du matériel quantique en :\n",
        "\n",
        "* Exprimer le circuit en utilisant les portes natives disponibles sur un système quantique\n",
        "* Cartographie des qubits virtuels en qubits physiques\n",
        "* Ajout de SWAP en fonction des besoins de connectivité\n",
        "* Optimisation des portes 1Q et 2Q\n",
        "* Ajout d'un découplage dynamique aux qubits inactifs pour prévenir les effets de la décohérence.\n",
        "\n",
        "Les primitives permettent d'utiliser des techniques de suppression des erreurs en définissant l'option `optimization_level` et en sélectionnant des options de transpilation avancées. Dans un cours ultérieur, nous étudierons différentes méthodes de construction de circuits pour améliorer les résultats, mais dans la plupart des cas, nous recommandons de régler `optimization_level=3`.\n",
        "\n",
        "Nous allons visualiser la valeur de l'optimisation croissante dans le processus de transpilation en examinant un exemple de circuit avec un comportement idéal simple.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "9e0bf552-32b0-477e-9ac3-4ab643c30623",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/9e0bf552-32b0-477e-9ac3-4ab643c30623-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 24,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit import Parameter, QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "theta = Parameter(\"theta\")\n",
        "\n",
        "qc = QuantumCircuit(2)\n",
        "qc.x(1)\n",
        "qc.h(0)\n",
        "qc.cp(theta, 0, 1)\n",
        "qc.h(0)\n",
        "observables = SparsePauliOp.from_list([(\"ZZ\", 1)])\n",
        "\n",
        "qc.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1c2b0408-e004-4a04-8aa5-cf519744082b",
      "metadata": {},
      "source": [
        "Le circuit ci-dessus peut produire des valeurs sinusoïdales de l'espérance de l'observable donnée, à condition d'insérer des phases couvrant un intervalle approprié, tel que $[0,2\\pi]$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "99b23f88-ddcb-45dc-a86d-3d48ff990319",
      "metadata": {},
      "outputs": [],
      "source": [
        "## Setup phases\n",
        "import numpy as np\n",
        "\n",
        "phases = np.linspace(0, 2 * np.pi, 50)\n",
        "\n",
        "# phases need to be expressed as a list of lists in order to work\n",
        "individual_phases = [[phase] for phase in phases]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ca040383-9c8c-4a38-8e40-bad1b26c6085",
      "metadata": {},
      "source": [
        "Nous pouvons utiliser un simulateur pour montrer l'utilité d'une transpilation optimisée. Nous reviendrons plus loin sur l'utilisation de matériel réel pour démontrer l'utilité de l'atténuation des erreurs. Nous utiliserons QiskitRuntimeService pour obtenir un backend réel (dans ce cas, ibm\\_brisbane), et utiliserons AerSimulator pour simuler ce backend, y compris son comportement en matière de bruit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6f0e6f32-02f7-471f-a9f3-f30d22f8cca4",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "# get a real backend from the runtime service\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# generate a simulator that mimics the real quantum system with the latest calibration results\n",
        "backend_sim = AerSimulator.from_backend(backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "38df43e8-b11e-4671-b8fb-7a2eb74f648a",
      "metadata": {},
      "source": [
        "Nous pouvons désormais utiliser un gestionnaire de passes pour transcompiler le circuit dans l'« architecture de jeu d'instructions » (ISA) du backend. Il s'agit d'une nouvelle exigence du service de calcul d' IBM Quantum : tous les circuits soumis à un backend doivent respecter les contraintes de la cible de ce dernier, ce qui signifie qu'ils doivent être écrits en fonction de l'ISA du backend, c'est-à-dire de l'ensemble des instructions que le dispositif est capable de comprendre et d'exécuter. Ces contraintes cibles sont définies par des facteurs tels que les portes de base natives du dispositif, la connectivité de ses qubits et, le cas échéant, les spécifications relatives à la synchronisation des impulsions et autres instructions.\n",
        "\n",
        "Notez que dans le cas présent, nous effectuerons cette opération deux fois : une fois avec le niveau d'optimisation = 0, et une fois avec le niveau d'optimisation fixé à 3. À chaque fois, nous utiliserons la primitive Estimator pour estimer les valeurs de l'espérance de l'observable à différentes valeurs de la phase.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4dc699cd-0bb4-4b48-adcc-0bbe99218a26",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Import estimator and specify that we are using the simulated backend:\n",
        "\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
        "\n",
        "estimator = Estimator(mode=backend_sim)\n",
        "\n",
        "circuit = qc"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7990eb62-879d-4beb-b452-f3d86b164d11",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Use a pass manager to transpile the circuit and observable for the backend being simulated.\n",
        "# Start with no optimization:\n",
        "\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=0)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "noisy_exp_values = []\n",
        "pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "cost = estimator.run([pub]).result()[0].data.evs\n",
        "noisy_exp_values = cost[0]\n",
        "\n",
        "# Repeat above steps, but now with optimization = 3:\n",
        "\n",
        "exp_values_with_opt_es = []\n",
        "pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=3)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "cost = estimator.run([pub]).result()[0].data.evs\n",
        "exp_values_with_opt_es = cost[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0cc8d4ea-81ef-4146-b8ed-9c7edae72be3",
      "metadata": {},
      "source": [
        "Enfin, nous pouvons tracer les résultats, et nous voyons que la précision du calcul était assez bonne même sans optimisation, mais qu'elle s'est nettement améliorée en augmentant l'optimisation au niveau 3. Notez que dans les circuits plus profonds et plus compliqués, la différence entre les niveaux d'optimisation 0 et 3 est susceptible d'être plus significative. Il s'agit d'un circuit très simple utilisé comme modèle de jouet.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "f3d3f805-f8d0-474d-9a88-2d1f164639b0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/f3d3f805-f8d0-474d-9a88-2d1f164639b0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(phases, noisy_exp_values, \"o\", label=\"opt=0\")\n",
        "plt.plot(phases, exp_values_with_opt_es, \"o\", label=\"opt=3\")\n",
        "plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label=\"ideal\")\n",
        "plt.ylabel(\"Expectation\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "2394d082-2278-48cf-95b0-3363ed723ec1",
      "metadata": {
        "gloss": {
          "bias": {
            "text": "A systematic drift in the measured quantities, usually caused by errors.",
            "title": "Bias"
          }
        }
      },
      "source": [
        "<span id=\"error-mitigation\" />\n",
        "\n",
        "### Atténuation des erreurs\n",
        "\n",
        "[La réduction des erreurs](/docs/guides/error-mitigation-and-suppression-techniques) désigne les techniques qui permettent aux utilisateurs de réduire les erreurs de circuit en modélisant le bruit du dispositif au moment de l'exécution. En général, cela se traduit par une charge de prétraitement quantique liée à l'entraînement du modèle et par une charge de post-traitement classique visant à corriger les erreurs présentes dans les résultats bruts à l'aide du modèle généré.\n",
        "\n",
        "L'option `resilience_level` de la primitive « IBM Quantum » définit le niveau de résilience à prendre en compte lors de la compilation face aux erreurs. Des niveaux plus élevés permettent d'obtenir des résultats plus précis, mais au prix d'un temps de traitement plus long en raison de la surcharge liée à l'échantillonnage quantique. Les niveaux de résilience permettent de définir le compromis entre coût et précision lors de l'application d'une atténuation des erreurs à votre requête primitive.\n",
        "\n",
        "Lors de la mise en œuvre d'une technique d'atténuation des erreurs, nous nous attendons à ce que le <DefinitionTooltip definition=\"Une dérive systématique des quantités mesurées, généralement causée par des erreurs.\">pondération</DefinitionTooltip> de nos résultats soit réduit par rapport au biais précédent, non atténué. Dans certains cas, le biais peut même disparaître. Mais cela a un coût. Au fur et à mesure que nous réduisons le biais dans nos quantités estimées, la variabilité statistique augmente (c'est-à-dire la variance), ce dont nous pouvons tenir compte en augmentant encore le nombre de tirs par circuit dans notre processus d'échantillonnage. Cela entraînera une surcharge au-delà de ce qui est nécessaire pour réduire le biais, c'est pourquoi cela n'est pas fait par défaut. Nous pouvons facilement opter pour ce comportement en ajustant le nombre de tirs par circuit dans options.executions.shots, comme le montre l'exemple ci-dessous.\n",
        "\n",
        "![Diagramme montrant des distributions plus larges ou plus étroites, comme dans le compromis biais /var iance.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-bias-variance-trade-off.svg)\n",
        "\n",
        "Dans le cadre de ce cours, nous allons étudier ces modèles d’atténuation des erreurs à un niveau général afin d’illustrer les mécanismes d’atténuation des erreurs que les primitives de l’ IBM Quantum peuvent mettre en œuvre, sans entrer dans les détails de leur implémentation.\n",
        "\n",
        "<span id=\"twirled-readout-error-extinction-t-rex\" />\n",
        "\n",
        "### Extinction de l'erreur de lecture par rotation (T-REx)\n",
        "\n",
        "L'extinction des erreurs de lecture par tournoiement (T-REx) utilise une technique connue sous le nom de tournoiement de Pauli pour réduire le bruit introduit au cours du processus de mesure quantique. Cette technique ne suppose aucune forme spécifique de bruit, ce qui la rend très générale et efficace.\n",
        "\n",
        "Flux de travail global :\n",
        "\n",
        "1. Acquisition de données pour l'état zéro avec retournement aléatoire des bits (Pauli X avant la mesure)\n",
        "2. Acquérir des données pour l'état désiré (bruyant) avec des retournements de bits aléatoires (Pauli X avant la mesure)\n",
        "3. Calculer la fonction spéciale pour chaque ensemble de données et diviser.\n",
        "\n",
        " \n",
        "\n",
        "![Schéma montrant les circuits de mesure et d'étalonnage du T-REX.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-trex-data-collection.svg)\n",
        "\n",
        "Nous pouvons le faire avec `options.resilience_level = 1`, comme le montre l'exemple ci-dessous.\n",
        "\n",
        "<span id=\"zero-noise-extrapolation\" />\n",
        "\n",
        "### Extrapolation sans bruit\n",
        "\n",
        "L'extrapolation à bruit nul (ZNE) consiste d'abord à amplifier le bruit dans le circuit qui prépare l'état quantique souhaité, à obtenir des mesures pour plusieurs niveaux de bruit différents et à utiliser ces mesures pour déduire le résultat sans bruit.\n",
        "\n",
        "Flux de travail global :\n",
        "\n",
        "1. Amplifier le bruit du circuit pour plusieurs facteurs de bruit\n",
        "2. Exécuter tous les circuits amplifiés par le bruit\n",
        "3. Extrapolation jusqu'à la limite du bruit zéro\n",
        "\n",
        " \n",
        "\n",
        "![Un diagramme montrant les étapes de ZNE. Le bruit est artificiellement amplifié par différents facteurs. Les valeurs sont ensuite extrapolées à ce qu'elles devraient être à bruit nul.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-zne-stages.svg)\n",
        "\n",
        "On peut configurer cela avec `options.resilience_level = 2`. Nous pourrions optimiser davantage cette approche en explorant diverses valeurs de `noise_factors`, `noise_amplifiers`, et `extrapolators`, mais cela dépasse le cadre de ce cours. Nous vous invitons à tester ces [options comme décrit ici](/docs/guides/error-mitigation-and-suppression-techniques).\n",
        "\n",
        "Chaque méthode s'accompagne de son propre surcoût : un compromis entre le nombre de calculs quantiques nécessaires (temps) et la précision de nos résultats :\n",
        "\n",
        "$$\n",
        "\\begin{array}{c|c|c|c}\n",
        "  \\text{Methods} & R=1 \\text{, T-REx} & R=2 \\text{, ZNE} \\\\[1mm]\n",
        "  \\hline\n",
        "  \\text{Assumptions} & \\text{None} & \\text{Ability to scale noise} \\\\[1mm]\n",
        "  \\text{Qubit overhead} & 1 & 1 \\\\[1mm]\n",
        "  \\text{Sampling overhead} & 2 & N_{\\text{noise-factors}} \\\\[1mm]\n",
        "  \\text{Bias} & 0 & \\mathcal{O}(\\lambda^{N_{\\text{noise-factors}}}) \\\\[1mm]\n",
        "\\end{array}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "8b46120f-5312-4450-bf51-13be198428fa",
      "metadata": {},
      "source": [
        "<span id=\"using-the-options-in-quantum-compute-for-mitigation-and-suppression\" />\n",
        "\n",
        "### Utilisation des options de Quantum Compute pour l'atténuation et la suppression\n",
        "\n",
        "Voici comment calculer une valeur attendue tout en utilisant les fonctionnalités d'atténuation et de suppression des erreurs dans le service de calcul d' IBM Quantum. Nous pouvons utiliser exactement le même circuit et la même grandeur observable que précédemment, mais cette fois-ci en maintenant le niveau d'optimisation fixé à 2, et en ajustant la *résilience* ou la ou les techniques d'atténuation des erreurs utilisées. Ce processus d'atténuation des erreurs se produit à plusieurs reprises au cours d'une boucle d'optimisation.\n",
        "\n",
        "Nous réalisons cette partie sur du matériel réel, car l'atténuation des erreurs n'est pas disponible sur les simulateurs.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "57a6b723-6e73-4693-95bf-f0782a8eb854",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Estimated usage: 8 minutes, benchmarked on an Eagle processor, 5-23-24\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import (\n",
        "    Session,\n",
        "    EstimatorOptions,\n",
        "    EstimatorV2 as Estimator,\n",
        ")\n",
        "\n",
        "# We select the least busy backend\n",
        "\n",
        "# Select the least busy backend\n",
        "# backend = service.least_busy(\n",
        "#    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        "# )\n",
        "\n",
        "# Or use a specific backend\n",
        "backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Initialize some variables to save the results from different runs:\n",
        "\n",
        "exp_values_with_em0_es = []\n",
        "exp_values_with_em1_es = []\n",
        "exp_values_with_em2_es = []\n",
        "\n",
        "# Use a pass manager to optimize the circuit and observables for the backend chosen:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=2)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "# Open a session and run with no error mitigation:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=0, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em0_es = cost[0]\n",
        "\n",
        "# Open a session and run with resilience = 1:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em1_es = cost[0]\n",
        "\n",
        "# Open a session and run with resilience = 2:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=2, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em2_es = cost[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "48215ea6-3045-4880-8671-265e2fb33e5e",
      "metadata": {},
      "source": [
        "Comme précédemment, nous pouvons tracer les valeurs d'espérance résultantes en fonction de l'angle de phase pour les trois niveaux d'atténuation des erreurs utilisés. Avec beaucoup de difficultés, on peut constater que l'atténuation des erreurs améliore légèrement les résultats. Là encore, cet effet est beaucoup plus prononcé dans les circuits plus profonds et plus compliqués.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 93,
      "id": "474f1a1a-ee90-468d-9390-fdb455aeb142",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/474f1a1a-ee90-468d-9390-fdb455aeb142-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(phases, exp_values_with_em0_es, \"o\", label=\"unmitigated\")\n",
        "plt.plot(phases, exp_values_with_em1_es, \"o\", label=\"resil = 1\")\n",
        "plt.plot(phases, exp_values_with_em2_es, \"o\", label=\"resil = 2\")\n",
        "plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label=\"ideal\")\n",
        "plt.ylabel(\"Expectation\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "1e683c07-96f3-47d5-8981-8d3aeb8be81f",
      "metadata": {},
      "source": [
        "<span id=\"summary\" />\n",
        "\n",
        "## Récapitulatif\n",
        "\n",
        "Cette leçon vous a appris à créer une fonction de coût :\n",
        "\n",
        "* Créer une fonction de coût\n",
        "* Comment tirer parti des primitives d' IBM Quantum s pour atténuer et supprimer le bruit\n",
        "* Comment définir une stratégie de mesure pour optimiser la rapidité par rapport à la précision?\n",
        "\n",
        "Voici notre charge de travail variationnelle de haut niveau :\n",
        "\n",
        "![Un diagramme montrant le circuit quantique avec des unités préparant l'état de référence et l'état variationnel, suivi de mesures. Ils sont utilisés pour évaluer la fonction de coût.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-circuit.svg)\n",
        "\n",
        "Notre fonction de coût s'exécute à chaque itération de la boucle d'optimisation. La prochaine leçon examinera comment l'optimiseur classique utilise l'évaluation de notre fonction de coût pour sélectionner de nouveaux paramètres.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "2751c16d-a9c1-407b-ab88-a9dcf768bc2e",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.1.0\n",
            "0.23.0\n"
          ]
        }
      ],
      "source": [
        "import qiskit\n",
        "import qiskit_ibm_runtime\n",
        "\n",
        "print(qiskit.version.get_version_info())\n",
        "print(qiskit_ibm_runtime.version.get_version_info())"
      ]
    },
    {
      "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
}