{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "bfa8f443",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"L'algorithme Deutsch-Jozsa\"\n",
        "description: \"Découvrez comment l'algorithme Deutsch-Jozsa utilise le parallélisme quantique et l'interférence pour atteindre une accélération exponentielle par rapport aux algorithmes classiques.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore blackbox  Hadamards */}\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e761a401-3dd0-4c3c-9333-0d89da48fb34",
      "metadata": {},
      "source": [
        "<span id=\"the-deutsch-jozsa-algorithm\" />\n",
        "\n",
        "# L'algorithme Deutsch-Jozsa\n",
        "\n",
        "Pour ce module Qiskit en classe, les étudiants doivent disposer d'un environnement Python fonctionnel avec les paquets suivants installés :\n",
        "\n",
        "* `qiskit` v2.1.0 ou plus récent\n",
        "* `qiskit-ibm-runtime` v0.40.1 ou plus récent\n",
        "* `qiskit-aer` v0.17.0 ou plus récent\n",
        "* `qiskit.visualization`\n",
        "* `numpy`\n",
        "* `pylatexenc`\n",
        "\n",
        "Pour configurer et installer les paquets ci-dessus, voir le guide d' [installation de Qiskit](/docs/guides/install-qiskit).\n",
        "Afin d'exécuter des tâches sur de véritables ordinateurs quantiques, les étudiants devront créer un compte sur IBM Quantum® en suivant les étapes du guide [Configurer votre compte IBM Cloud](/docs/guides/cloud-setup).\n",
        "\n",
        "Ce module a été testé et a utilisé quatre secondes de temps QPU. Il s'agit uniquement d'une estimation. L'utilisation réelle peut varier.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "24a83c6d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Uncomment and modify this line as needed to install dependencies\n",
        "#!pip install 'qiskit>=2.1.0' 'qiskit-ibm-runtime>=0.40.1' 'qiskit-aer>=0.17.0' 'numpy' 'pylatexenc'"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "026f7f82-ac54-413d-8158-e58461bc2afd",
      "metadata": {},
      "source": [
        "Regardez la présentation du module par le Dr. Katie McCormick ci-dessous, ou cliquez [ici](https://youtu.be/QcK0GK7DUh8?si=8e0Lmjgylxmgl2y7) pour la regarder sur YouTube.\n",
        "\n",
        "***\n",
        "\n",
        "<IBMVideo id=\"134413695\" title=\"Katie McCormick présente l'un des premiers algorithmes quantiques développés : l'algorithme de Deutsch et son extension, l'algorithme de Deutsch-Jozsa.\" />\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "34b2aac3-848f-46b4-8c95-8236b6ad7f8e",
      "metadata": {},
      "source": [
        "<span id=\"intro\" />\n",
        "\n",
        "## Introduction\n",
        "\n",
        "Au début des années 1980, les physiciens quantiques et les informaticiens avaient une vague idée que la mécanique quantique pouvait être exploitée pour effectuer des calculs bien plus puissants que ceux des ordinateurs classiques. Leur raisonnement est le suivant : il est difficile pour un ordinateur classique de simuler des systèmes quantiques, mais un ordinateur *quantique* devrait pouvoir le faire plus efficacement. Et si un ordinateur quantique peut simuler des systèmes quantiques plus efficacement, il peut peut-être accomplir d'autres tâches plus efficacement qu'un ordinateur classique.\n",
        "\n",
        "La logique était bonne, mais les détails restaient à régler. Cela a commencé en 1985, lorsque David Deutsch a décrit le premier \"ordinateur quantique universel\" Dans ce même article, il a fourni le premier exemple de problème pour lequel un ordinateur quantique pourrait résoudre quelque chose plus efficacement qu'un ordinateur classique. Ce premier exemple de jouet est aujourd'hui connu sous le nom d'\"algorithme de Deutsch\" L'amélioration de l'algorithme de Deutsch était modeste, mais Deutsch a travaillé avec Richard Jozsa quelques années plus tard pour creuser davantage l'écart entre les ordinateurs classiques et les ordinateurs quantiques.\n",
        "\n",
        "Ces algorithmes - celui de Deutsch et l'extension Deutsch-Jozsa - ne sont pas particulièrement utiles, mais ils restent très importants pour plusieurs raisons :\n",
        "\n",
        "1. Historiquement, ils ont été parmi les premiers algorithmes quantiques dont il a été démontré qu'ils surpassaient leurs homologues classiques. Les comprendre peut nous aider à comprendre comment la pensée de la communauté sur l'informatique quantique a évolué au fil du temps.\n",
        "2. Ils peuvent nous aider à comprendre certains aspects de la réponse à une question étonnamment subtile : Qu'est-ce qui donne à l'informatique quantique sa puissance? Les ordinateurs quantiques sont parfois comparés à des processeurs parallèles géants à échelle exponentielle. Mais ce n'est pas tout à fait exact. Si une partie de la réponse à cette question réside dans ce que l'on appelle le \"parallélisme quantique\", l'extraction d'un maximum d'informations en une seule fois est un art subtil. Les algorithmes de Deutsch et de Deutsch-Jozsa montrent comment cela est possible.\n",
        "\n",
        "Dans ce module, nous découvrirons l'algorithme de Deutsch, l'algorithme de Deutsch-Jozsa et ce qu'ils nous apprennent sur la puissance de l'informatique quantique.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "096da154-5663-4f46-8d9e-b6f163260706",
      "metadata": {},
      "source": [
        "<span id=\"quantum-parallelism-and-its-limits\" />\n",
        "\n",
        "## Le parallélisme quantique et ses limites\n",
        "\n",
        "Une partie de la puissance de l'informatique quantique provient du \"parallélisme quantique\" qui est essentiellement la capacité d'effectuer des opérations sur plusieurs entrées en même temps, puisque les états d'entrée des qubits peuvent être dans une superposition de plusieurs états classiquement autorisés. CEPENDANT, bien qu'un circuit quantique puisse être capable d'évaluer plusieurs états d'entrée à la fois, il est impossible d'extraire toutes ces informations en une seule fois.\n",
        "\n",
        "Pour comprendre ce que je veux dire, disons que nous avons un bit, $x$ et une fonction appliquée à ce bit, $f(x)$. Il existe quatre fonctions binaires possibles qui transforment un bit unique en un autre bit unique :\n",
        "\n",
        "| $x$ | $f_1(x)$ | $f_2(x)$ | $f_3(x)$ | $f_4(x)$ |\n",
        "| --- | -------- | -------- | -------- | -------- |\n",
        "| 0   | 0        | 0        | 1        | 1        |\n",
        "| 1   | 0        | 1        | 0        | 1        |\n",
        "\n",
        "Nous aimerions savoir laquelle de ces fonctions (1-4) est notre $f(x)$. Classiquement, nous devrions exécuter la fonction deux fois - une fois pour $x=0$, une fois pour $x=1$. Mais voyons si nous pouvons faire mieux avec un circuit quantique. Nous pouvons apprendre à connaître la fonction à l'aide de la porte suivante :\n",
        "\n",
        "![parallélisme quantique](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/deutsch-jozsa/quantum-parallelism.avif)\n",
        "\n",
        "Ici, la porte $U_f$ calcule $f(x)$, où $x$ est l'état du qubit 0, et l'applique au qubit 1. Ainsi, l'état résultant, $|x\\rangle|y\\oplus f(x)\\rangle$, devient simplement $|x\\rangle|f(x)\\rangle$ lorsque $|y\\rangle = |0\\rangle$. Ceci contient toutes les informations dont nous avons besoin pour connaître la fonction $f(x)$ : le qubit 0 nous dit ce qu'est $x$, et le qubit 1 nous dit ce qu'est $f(x)$. Ainsi, si nous initialisons $|x\\rangle = \\frac{1}{\\sqrt{2}}(|0\\rangle+|1\\rangle)$, l'état final des deux qubits sera : $|y\\rangle|x\\rangle = \\frac{1}{\\sqrt{2}}(|f(0)\\rangle|0\\rangle+|f(1)\\rangle|1\\rangle)$. Mais comment accéder à cette information?\n",
        "\n",
        "<span id=\"21-try-it-on-qiskit\" />\n",
        "\n",
        "### 2.1. Essayez-le sur Qiskit :\n",
        "\n",
        "À l'aide de Qiskit, nous sélectionnons au hasard l'une des quatre fonctions possibles ci-dessus et nous exécutons le circuit. Votre tâche consiste alors à utiliser les mesures du circuit quantique pour apprendre la fonction en un minimum d'essais.\n",
        "\n",
        "Dans cette première expérience et tout au long du module, nous utiliserons un cadre pour l'informatique quantique connu sous le nom de \"modèles Qiskit\", qui décompose les flux de travail en plusieurs étapes :\n",
        "\n",
        "* Etape 1 : Tracer un problème quantique à partir d'entrées classiques\n",
        "* Étape 2 : Optimisation du problème pour l'exécution quantique\n",
        "* Étape 3 : Exécution à l'aide des primitives « IBM Quantum »\n",
        "* Étape 4 : Post-traitement et analyse classique\n",
        "\n",
        "Commençons par installer quelques paquets indispensables, notamment les primitives de l' IBM Quantum. Nous choisirons également l'ordinateur quantique le moins sollicité parmi ceux dont nous disposons.\n",
        "\n",
        "Le code ci-dessous vous permet de sauvegarder vos données d'identification lors de la première utilisation. Veillez à supprimer ces informations du bloc-notes après l'avoir enregistré dans votre environnement, afin que vos informations d'identification ne soient pas accidentellement partagées lorsque vous partagez le bloc-notes. Voir [Configurer votre compte IBM Cloud](/docs/guides/initialize-account) et [Initialiser le service dans un environnement non fiable](/docs/guides/cloud-setup-untrusted) pour plus d'informations.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6ccc7364-7b6b-45f5-94b8-b1274006ee2f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "ibm_brisbane\n"
          ]
        }
      ],
      "source": [
        "# Load IBM Quantum Compute Service\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "\n",
        "# Load the Runtime primitive and session\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "\n",
        "# Syntax for first saving your token.  Delete these lines after saving your credentials.\n",
        "\n",
        "# QiskitRuntimeService.save_account(channel='ibm_quantum_platform',\n",
        "# instance = '<YOUR_IBM_INSTANCE_CRN>', token='<YOUR_API_KEY>', overwrite=True, set_as_default=True)\n",
        "# service = QiskitRuntimeService(channel='ibm_quantum_platform')\n",
        "\n",
        "# Load saved credentials\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "# Use the least busy backend, or uncomment the loading of a specific backend like \"ibm_brisbane\".\n",
        "# backend = service.least_busy(operational=True, simulator=False, min_num_qubits = 127)\n",
        "backend = service.backend(\"ibm_brisbane\")\n",
        "print(backend.name)\n",
        "\n",
        "\n",
        "sampler = Sampler(mode=backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9912a7c5-ce2b-4eaa-abe7-82ebd4e494c8",
      "metadata": {},
      "source": [
        "La cellule ci-dessous vous permettra de basculer entre l'utilisation du simulateur ou du matériel réel tout au long du carnet. Nous vous recommandons de l'exécuter maintenant :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "29468e63-ce36-4eb7-95b4-176788a97e54",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Load the backend sampler\n",
        "from qiskit.primitives import BackendSamplerV2\n",
        "\n",
        "# Load the Aer simulator and generate a noise model based on the currently-selected backend.\n",
        "from qiskit_aer import AerSimulator\n",
        "from qiskit_aer.noise import NoiseModel\n",
        "\n",
        "# Alternatively, load a fake backend with generic properties and define a simulator.\n",
        "\n",
        "\n",
        "noise_model = NoiseModel.from_backend(backend)\n",
        "\n",
        "# Define a simulator using Aer, and use it in Sampler.\n",
        "backend_sim = AerSimulator(noise_model=noise_model)\n",
        "sampler_sim = BackendSamplerV2(backend=backend_sim)\n",
        "\n",
        "# You could also define a simulator-based sampler using a generic backend:\n",
        "# backend_gen = GenericBackendV2(num_qubits=18)\n",
        "# sampler_gen = BackendSamplerV2(backend=backend_gen)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "19e9b62f-6e1c-43a1-bdda-75766b1ff7d3",
      "metadata": {},
      "source": [
        "Maintenant que nous avons chargé les paquets nécessaires, nous pouvons procéder à la mise en œuvre des modèles Qiskit. Dans l'étape de mise en correspondance ci-dessous, nous créons d'abord une fonction qui sélectionne parmi les quatre fonctions possibles qui transforment un bit unique en un autre bit unique.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5e67183b-42b9-44c2-bd4b-b5e2d192a796",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/deutsch-jozsa/extracted-outputs/5e67183b-42b9-44c2-bd4b-b5e2d192a796-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Step 1: Map\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "\n",
        "qc = QuantumCircuit(2)\n",
        "\n",
        "\n",
        "def twobit_function(case: int):\n",
        "    \"\"\"\n",
        "    Generate a valid two-bit function as a `QuantumCircuit`.\n",
        "    \"\"\"\n",
        "    if case not in [1, 2, 3, 4]:\n",
        "        raise ValueError(\"`case` must be 1, 2, 3, or 4.\")\n",
        "\n",
        "    f = QuantumCircuit(2)\n",
        "    if case in [2, 3]:\n",
        "        f.cx(0, 1)\n",
        "    if case in [3, 4]:\n",
        "        f.x(1)\n",
        "    return f\n",
        "\n",
        "\n",
        "# first, convert oracle circuit (above) to a single gate for drawing purposes. otherwise, the\n",
        "# circuit is too large to display\n",
        "\n",
        "# you may edit the number inside \"twobit_function()\" to select among the four valid functions:\n",
        "# blackbox = twobit_function(2).to_gate()\n",
        "\n",
        "# blackbox.label = \"$U_f$\"\n",
        "\n",
        "qc.h(0)\n",
        "qc.barrier()\n",
        "qc.compose(twobit_function(2), inplace=True)\n",
        "qc.measure_all()\n",
        "\n",
        "\n",
        "qc.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5cf08592-f32e-4ae8-ad69-afb160e43ab4",
      "metadata": {},
      "source": [
        "Dans le circuit ci-dessus, la porte de Hadamard \"H\" fait passer le qubit 0, qui est initialement dans l'état $|0\\rangle$, à l'état de superposition $\\frac{1}{\\sqrt{2}}(|0\\rangle+|1\\rangle)$. Ensuite, $U_f$ évalue la fonction $f(x)$ et l'applique au qubit 1.\n",
        "\n",
        "Ensuite, nous devons optimiser et transpiler le circuit pour l'exécuter sur l'ordinateur quantique :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "d8d77417-0295-4f20-aff6-b2a007d5d02f",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 2: Transpile\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "\n",
        "qc_isa = pm.run(qc)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "51e7b705-a4b8-460a-aa5a-6122c84f9b2f",
      "metadata": {},
      "source": [
        "Enfin, nous exécutons notre circuit transpilé sur l'ordinateur quantique et visualisons nos résultats :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0495256b-2a80-422e-9adf-2fef1c039a6d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 3: Run the job on a real quantum computer\n",
        "\n",
        "job = sampler.run([qc_isa], shots=1)\n",
        "# job = sampler_sim.run([qc_isa],shots=1) # uncomment this line to run on simulator instead\n",
        "res = job.result()\n",
        "counts = res[0].data.meas.get_counts()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "6d2904cc-c730-4dca-a167-438018230299",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/deutsch-jozsa/extracted-outputs/6d2904cc-c730-4dca-a167-438018230299-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Step 4: Visualize and analyze results\n",
        "\n",
        "## Analysis\n",
        "from qiskit.visualization import plot_histogram\n",
        "\n",
        "plot_histogram(counts)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "47710bb8-372c-4949-9468-bd2480c4ae5b",
      "metadata": {},
      "source": [
        "L'histogramme ci-dessus représente nos résultats. Selon le nombre de tirs que vous avez choisi pour exécuter le circuit à l'étape 3 ci-dessus, vous pouvez voir une ou deux barres, représentant les états mesurés des deux qubits à chaque tir. Comme toujours avec Qiskit et dans ce carnet, nous utilisons la notation \"little endian\", ce qui signifie que les états des qubits 0 à n sont écrits dans l'ordre croissant de droite à gauche, de sorte que le qubit 0 est toujours le plus à droite.\n",
        "\n",
        "Ainsi, comme le qubit 0 était dans un état de superposition, le circuit a évalué la fonction *pour* $x=0$ et $x=1$ *en même temps* — quelque chose que les ordinateurs classiques ne peuvent pas faire! Mais le problème se pose lorsque nous voulons en savoir plus sur la fonction $f(x)$ - lorsque nous mesurons les qubits, leur état s'effondre. Si vous sélectionnez \"shots = 1\" pour n'exécuter le circuit qu'une seule fois, vous ne verrez qu'une seule barre dans l'histogramme ci-dessus et vos informations sur la fonction seront incomplètes.\n",
        "\n",
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Vérifiez votre compréhension\n",
        "\n",
        "Combien de fois devons-nous exécuter l'algorithme ci-dessus pour apprendre la fonction $f(x)$? Cette méthode est-elle meilleure que la méthode classique? Préféreriez-vous un ordinateur classique ou quantique pour résoudre ce problème?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Réponse\">\n",
        "    Étant donné que la mesure effondrera la superposition et ne renverra qu'une seule valeur, nous devons exécuter le circuit *au moins* deux fois pour renvoyer les deux sorties de la fonction $f(0)$ et $f(1)$. Dans le meilleur des cas, les résultats sont aussi bons que dans le cas classique, où nous calculons à la fois $f(0)$ et $f(1)$ dans les deux premières requêtes. Mais il est possible que nous devions l'exécuter plus de deux fois, car la mesure finale est probabiliste et peut renvoyer la même valeur $f(x)$ les deux premières fois. Dans ce cas, je préférerais un ordinateur classique.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Ainsi, si le parallélisme quantique peut être puissant lorsqu'il est utilisé à bon escient, il n'est pas correct de dire qu'un ordinateur quantique fonctionne comme un processeur parallèle classique massif. L'acte de mesure effondre les états quantiques, de sorte que nous ne pouvons jamais accéder qu'à un seul résultat du calcul.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cda50fdf-c354-4021-9b5a-c8cde5cc5edd",
      "metadata": {},
      "source": [
        "<span id=\"deutschs-algorithm\" />\n",
        "\n",
        "## Algorithme de Deutsch\n",
        "\n",
        "Bien que le parallélisme quantique ne nous donne pas à lui seul un avantage sur les ordinateurs classiques, nous pouvons l'associer à un autre phénomène quantique, l'interférence, pour obtenir une accélération. L'algorithme connu aujourd'hui sous le nom d'\"algorithme de Deutsch\" est le premier exemple d'algorithme permettant d'atteindre cet objectif.\n",
        "\n",
        "<span id=\"the-problem\" />\n",
        "\n",
        "### Le problème\n",
        "\n",
        "C'est là que le bât blesse :\n",
        "\n",
        "Étant donné un bit d'entrée, $x = \\{0,1\\}$, et une fonction d'entrée $f(x) = \\{0,1\\}$, déterminez si la fonction est *équilibrée* ou *constante*. En d'autres termes, si elle est équilibrée, la sortie de la fonction est 0 la moitié du temps et 1 l'autre moitié du temps. S'il est constant, la sortie de la fonction est soit toujours 0, soit toujours 1. Rappelons le tableau des quatre fonctions possibles prenant un bit unique pour un autre bit unique :\n",
        "\n",
        "| $x$ | $f_1(x)$ | $f_2(x)$ | $f_3(x)$ | $f_4(x)$ |\n",
        "| --- | -------- | -------- | -------- | -------- |\n",
        "| 0   | 0        | 0        | 1        | 1        |\n",
        "| 1   | 0        | 1        | 0        | 1        |\n",
        "\n",
        "La première et la dernière fonction, $f_1(x)$ et $f_4(x)$, sont constantes, tandis que les deux fonctions intermédiaires, $f_2(x)$ et $f_3(x)$, sont équilibrées.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a34f1c24-0ed5-4458-8d4e-5957c691cadb",
      "metadata": {},
      "source": [
        "<span id=\"the-algorithm\" />\n",
        "\n",
        "### L'algorithme\n",
        "\n",
        "Deutsch a abordé ce problème par le biais du \"modèle de requête\" Dans le modèle d'interrogation, la fonction d'entrée ( $f_i(x)$ ci-dessus) est contenue dans une \"boîte noire\" - nous n'avons pas d'accès direct à son contenu, mais nous pouvons interroger la boîte noire et elle nous donnera la sortie de la fonction. On dit parfois qu'un \"oracle\" fournit ces informations. Voir la [leçon 1 : Algorithmes de requête quantique](/learning/courses/fundamentals-of-quantum-algorithms/quantum-query-algorithms/introduction) du cours Fondamentaux de l'algorithmique quantique pour en savoir plus sur le modèle de requête.\n",
        "\n",
        "Pour déterminer si un algorithme quantique est plus efficace qu'un algorithme classique dans le modèle d'interrogation, il suffit de comparer le nombre d'interrogations de la boîte noire dans chaque cas. Dans le cas classique, pour savoir si la fonction contenue dans la boîte noire est équilibrée ou constante, il faudrait interroger la boîte deux fois pour obtenir $f(0)$ et $f(1)$.\n",
        "\n",
        "Dans l'algorithme quantique de Deutsch, il a trouvé un moyen d'obtenir l'information avec une seule requête! Il a apporté une modification au circuit de \"parallélisme quantique\" ci-dessus, afin de préparer un état de superposition sur les *deux* qubits, au lieu du seul qubit 0. Ensuite, les deux sorties de la fonction, $f(0)$ et $f(1)$, ont interféré pour renvoyer 0 si elles étaient toutes les deux 0 ou toutes les deux 1 (la fonction était constante), et ont renvoyé 1 si elles étaient différentes (la fonction était équilibrée). Deutsch pouvait ainsi faire la différence entre une fonction constante et une fonction équilibrée à l'aide d'une seule requête.\n",
        "\n",
        "Voici un schéma de l'algorithme de Deutsch :\n",
        "\n",
        "![Schéma de l'algorithme de Deutsch](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/deutsch-jozsa/Deutsch_algo.avif)\n",
        "\n",
        "Pour comprendre le fonctionnement de cet algorithme, examinons les états quantiques des qubits aux trois points notés sur le diagramme ci-dessus. Essayez de trouver les états par vous-même avant de cliquer pour voir les réponses :\n",
        "\n",
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Vérifiez votre compréhension\n",
        "\n",
        "Quel est l'état $|\\pi_1\\rangle$?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Réponse\">\n",
        "    L'application d'une transformation de Hadamard transforme l'état $|0\\rangle$ en $\\frac{1}{\\sqrt{2}}(|0\\rangle+|1\\rangle)$ et l'état $|1\\rangle$ en $\\frac{1}{\\sqrt{2}}(|0\\rangle-|1\\rangle)$. L'état complet devient donc $|\\pi_1\\rangle = [\\frac{|0\\rangle-|1\\rangle}{\\sqrt{2}}][\\frac{|0\\rangle+|1\\rangle}{\\sqrt{2}}]$\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Quel est l'état $|\\pi_2\\rangle$?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Réponse\">\n",
        "    Avant d'appliquer $U_f$, rappelez-vous ce qu'il fait. Il modifiera l'état du qubit 1 en fonction de l'état du qubit 0. Il est donc logique de factoriser l'état du qubit 0 : $|\\pi_1\\rangle = \\frac{1}{2} (|0\\rangle-|1\\rangle)|0\\rangle+\\frac{1}{2}(|0\\rangle-|1\\rangle)|1\\rangle$. Ensuite, si $f(0)=f(1)$, les deux termes se transformeront de la même manière et le signe relatif entre les deux termes restera positif, mais si $f(0)\\neq f(1)$, cela signifie que le deuxième terme prendra un signe négatif par rapport au premier terme, changeant l'état du qubit 0 de $\\frac{1}{\\sqrt{2}}(|0\\rangle+|1\\rangle)$ à $\\frac{1}{\\sqrt{2}}(|0\\rangle-|1\\rangle)$. Donc :\n",
        "\n",
        "    $$\n",
        "    |\\pi_2\\rangle = \\begin{cases}\n",
        "    \\pm[\\frac{|0\\rangle-|1\\rangle}{\\sqrt{2}}][\\frac{|0\\rangle+|1\\rangle}{\\sqrt{2}}] & \\text{if} & f(0) = f(1) \\\\\n",
        "    \\pm[\\frac{|0\\rangle-|1\\rangle}{\\sqrt{2}}][\\frac{|0\\rangle-|1\\rangle}{\\sqrt{2}}] &\\text{if} & f(0) \\neq f(1) \\\\\n",
        "    \\end{cases}\n",
        "    $$\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Quel est l'état $|\\pi_3\\rangle$?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Réponse\">\n",
        "    Maintenant, l'état du qubit 0 est soit $\\frac{1}{\\sqrt{2}}(|0\\rangle+|1\\rangle)$ soit $\\frac{1}{\\sqrt{2}}(|0\\rangle-|1\\rangle)$, selon la fonction. L'application de la méthode de Hadamard permet d'obtenir $|0\\rangle$ ou $|1\\rangle$, respectivement.\n",
        "\n",
        "    $$\n",
        "    |\\pi_3\\rangle = \\begin{cases}\n",
        "    \\pm[\\frac{|0\\rangle-|1\\rangle}{\\sqrt{2}}]|0\\rangle & \\text{if} & f(0) = f(1) \\\\\n",
        "    \\pm[\\frac{|0\\rangle-|1\\rangle}{\\sqrt{2}}]|1\\rangle &\\text{if} & f(0) \\neq f(1) \\\\\n",
        "    \\end{cases}\n",
        "    $$\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "En examinant vos réponses aux questions ci-dessus, vous constaterez qu'il se passe quelque chose d'un peu surprenant. Bien que $U_f$ ne fasse rien explicitement à l'état du qubit 0, parce qu'il modifie le qubit 1 en fonction de l'état du qubit 0, il peut arriver que cela provoque un déphasage dans le qubit 0. Ce phénomène est connu sous le nom de \"phase-kickback\" et est discuté plus en détail dans la [leçon 1 : Algorithmes d'interrogation quantique](/learning/courses/fundamentals-of-quantum-algorithms/quantum-query-algorithms/introduction) du cours Fondamentaux de l'algorithmique quantique.\n",
        "\n",
        "Maintenant que nous comprenons le fonctionnement de cet algorithme, mettons-le en œuvre avec Qiskit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4d9129df-f2ef-4f94-9508-21ed986fd823",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/deutsch-jozsa/extracted-outputs/4d9129df-f2ef-4f94-9508-21ed986fd823-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "## Deutsch's algorithm:\n",
        "\n",
        "## Step 1: Map the problem\n",
        "\n",
        "# first, convert oracle circuit (above) to a single gate for drawing purposes.\n",
        "# otherwise, the circuit is too large to display\n",
        "blackbox = twobit_function(\n",
        "    3\n",
        "    # you may edit the number (1-4) inside \"twobit_function()\" to select among the four valid functions\n",
        ").to_gate()\n",
        "blackbox.label = \"$U_f$\"\n",
        "\n",
        "\n",
        "qc_deutsch = QuantumCircuit(2, 1)\n",
        "\n",
        "qc_deutsch.x(1)\n",
        "qc_deutsch.h(range(2))\n",
        "\n",
        "qc_deutsch.barrier()\n",
        "qc_deutsch.compose(twobit_function(2), inplace=True)\n",
        "qc_deutsch.barrier()\n",
        "\n",
        "qc_deutsch.h(0)\n",
        "qc_deutsch.measure(0, 0)\n",
        "\n",
        "qc_deutsch.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "ef0196b4-d4f0-4581-96f8-97893e652ee8",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 2: Transpile\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "\n",
        "qc_isa = pm.run(qc_deutsch)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "51ad01d0-fa90-4e80-a55d-e55e146e2065",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 3: Run the job on a real quantum computer\n",
        "\n",
        "job = sampler.run([qc_isa], shots=1)\n",
        "# job = sampler_sim.run([qc_isa],shots=1) # uncomment this line to run on simulator instead\n",
        "res = job.result()\n",
        "counts = res[0].data.c.get_counts()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "5465d833-49e0-4779-94a3-0adb18f6aa76",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "{'1': 1}\n",
            "balanced\n"
          ]
        }
      ],
      "source": [
        "# Step 4: Visualize and analyze results\n",
        "\n",
        "## Analysis\n",
        "print(counts)\n",
        "if \"1\" in counts:\n",
        "    print(\"balanced\")\n",
        "else:\n",
        "    print(\"constant\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "82f6da25-0f9c-47b3-aa24-02482f008383",
      "metadata": {},
      "source": [
        "<span id=\"the-deutsch-jozsa-algorithm\" />\n",
        "\n",
        "## L'algorithme Deutsch-Jozsa\n",
        "\n",
        "L'algorithme de Deutsch a constitué une première étape importante dans la démonstration de la manière dont un ordinateur quantique pourrait être plus efficace qu'un ordinateur classique, mais il ne s'agissait que d'une amélioration modeste : il ne nécessitait qu'une seule requête, contre deux dans le cas classique. En 1992, Deutsch et son collègue Richard Jozsa ont étendu l'algorithme original à deux qubits à un plus grand nombre de qubits. Le problème reste le même : déterminer si une fonction est *équilibrée* ou *constante*. Mais cette fois, la fonction passe de $n$ bits à un seul bit. Soit la fonction renvoie 0 et 1 un nombre égal de fois (elle est *équilibrée* ), soit la fonction renvoie toujours 1 ou toujours 0 (elle est *constante* ).\n",
        "\n",
        "Voici un schéma de l'algorithme :\n",
        "\n",
        "![DJ\\_algo.png](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/deutsch-jozsa/DJ_algo.avif)\n",
        "\n",
        "Cet algorithme fonctionne de la même manière que l'algorithme de Deutsch : le rebond de phase permet de lire l'état du qubit 0 pour déterminer si la fonction est constante ou équilibrée. C'est un peu plus difficile à voir que dans le cas de l'algorithme de Deutsch à deux qubits, puisque les états incluront des sommes sur les qubits $n$, et donc le calcul de ces états sera laissé comme un exercice optionnel pour vous à la fin du module. L'algorithme renvoie une chaîne de bits contenant tous les 0 si la fonction est constante, et une chaîne de bits contenant au moins un 1 si la fonction est équilibrée.\n",
        "\n",
        "Pour voir comment l'algorithme fonctionne dans Qiskit, nous devons d'abord générer notre oracle : la fonction aléatoire qui est garantie comme étant soit constante, soit équilibrée. Le code ci-dessous génère une fonction équilibrée dans 50 % des cas et une fonction constante dans 50 % des cas. Ne vous inquiétez pas si vous ne suivez pas entièrement le code - il est compliqué et n'est pas nécessaire à notre compréhension de l'algorithme quantique.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "ca2a51c0-3e62-4536-b891-0834e325a3d6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/deutsch-jozsa/extracted-outputs/ca2a51c0-3e62-4536-b891-0834e325a3d6-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "import numpy as np\n",
        "\n",
        "\n",
        "def dj_function(num_qubits):\n",
        "    \"\"\"\n",
        "    Create a random Deutsch-Jozsa function.\n",
        "    \"\"\"\n",
        "\n",
        "    qc_dj = QuantumCircuit(num_qubits + 1)\n",
        "    if np.random.randint(0, 2):\n",
        "        # Flip output qubits with 50% chance\n",
        "        qc_dj.x(num_qubits)\n",
        "    if np.random.randint(0, 2):\n",
        "        # return constant circuit with 50% chance.\n",
        "        return qc_dj\n",
        "\n",
        "    # If the \"if\" statement above was \"TRUE\" then we've returned the constant\n",
        "    # function and the function is complete. If not, we proceed in creating our\n",
        "    # balanced function. Everything below is to produce the balanced function:\n",
        "\n",
        "    # select half of all possible states at random:\n",
        "    on_states = np.random.choice(\n",
        "        range(2**num_qubits),  # numbers to sample from\n",
        "        2**num_qubits // 2,  # number of samples\n",
        "        replace=False,  # makes sure states are only sampled once\n",
        "    )\n",
        "\n",
        "    def add_cx(qc_dj, bit_string):\n",
        "        for qubit, bit in enumerate(reversed(bit_string)):\n",
        "            if bit == \"1\":\n",
        "                qc_dj.x(qubit)\n",
        "        return qc_dj\n",
        "\n",
        "    for state in on_states:\n",
        "        # qc_dj.barrier()  # Barriers are added to help visualize how the functions are created.\n",
        "        # They can safely be removed.\n",
        "        qc_dj = add_cx(qc_dj, f\"{state:0b}\")\n",
        "        qc_dj.mcx(list(range(num_qubits)), num_qubits)\n",
        "        qc_dj = add_cx(qc_dj, f\"{state:0b}\")\n",
        "\n",
        "    # qc_dj.barrier()\n",
        "\n",
        "    return qc_dj\n",
        "\n",
        "\n",
        "n = 3  # number of input qubits\n",
        "\n",
        "oracle = dj_function(n)\n",
        "\n",
        "display(oracle.draw(\"mpl\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "78096e00-a29b-418c-a620-726675c2a792",
      "metadata": {},
      "source": [
        "Il s'agit de la fonction oracle, qui est soit équilibrée, soit constante. Pouvez-vous voir en l'observant si la sortie du dernier qubit dépend des valeurs introduites pour les premiers $n$ qubits? Si la sortie du dernier qubit dépend des premiers $n$ qubits, pouvez-vous dire si cette sortie dépendante est équilibrée ou non?\n",
        "\n",
        "Nous pouvons dire si la fonction est équilibrée ou constante en regardant le circuit ci-dessus, mais n'oubliez pas que pour ce problème, nous considérons cette fonction comme une \"boîte noire\" Nous ne pouvons pas jeter un coup d'œil dans la boîte pour voir le schéma du circuit. Au lieu de cela, nous devons interroger la boîte.\n",
        "\n",
        "Pour interroger la boîte, nous utilisons l'algorithme de Deutsch-Jozsa et déterminons si la fonction est constante ou équilibrée :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "fe7ee688-f052-4a7e-bcc7-a14bea57e5c6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/deutsch-jozsa/extracted-outputs/fe7ee688-f052-4a7e-bcc7-a14bea57e5c6-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 12,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "blackbox = oracle.to_gate()\n",
        "blackbox.label = \"$U_f$\"\n",
        "\n",
        "\n",
        "qc_dj = QuantumCircuit(n + 1, n)\n",
        "qc_dj.x(n)\n",
        "qc_dj.h(range(n + 1))\n",
        "qc_dj.barrier()\n",
        "qc_dj.compose(blackbox, inplace=True)\n",
        "qc_dj.barrier()\n",
        "qc_dj.h(range(n))\n",
        "qc_dj.measure(range(n), range(n))\n",
        "\n",
        "qc_dj.decompose().decompose()\n",
        "\n",
        "\n",
        "qc_dj.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "bf3aedfa-7454-424e-85cb-c446a8918417",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/deutsch-jozsa/extracted-outputs/bf3aedfa-7454-424e-85cb-c446a8918417-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Step 1: Map the problem\n",
        "\n",
        "qc_dj = QuantumCircuit(n + 1, n)\n",
        "qc_dj.x(n)\n",
        "qc_dj.h(range(n + 1))\n",
        "qc_dj.barrier()\n",
        "qc_dj.compose(oracle, inplace=True)\n",
        "qc_dj.barrier()\n",
        "qc_dj.h(range(n))\n",
        "qc_dj.measure(range(n), range(n))\n",
        "\n",
        "qc_dj.decompose().decompose()\n",
        "\n",
        "\n",
        "qc_dj.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "5497c1aa-c427-419b-b22c-a0c2fa0c4028",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 2: Transpile\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "\n",
        "qc_isa = pm.run(qc_dj)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "974f3db9-1b55-414c-9fe4-d891cf22f78f",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 3: Run the job on a real quantum computer\n",
        "\n",
        "job = sampler.run([qc_isa], shots=1)\n",
        "# job = sampler_sim.run([qc_isa],shots=1) # uncomment this line to run on simulator instead\n",
        "res = job.result()\n",
        "counts = res[0].data.c.get_counts()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "39af76b4-f380-4a61-82a4-1e9203c20408",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "{'110': 1}\n",
            "balanced\n"
          ]
        }
      ],
      "source": [
        "# Step 4: Visualize and analyze results\n",
        "\n",
        "## Analysis\n",
        "print(counts)\n",
        "\n",
        "if (\n",
        "    \"0\" * n in counts\n",
        "):  # The D-J algorithm returns all zeroes if the function was constant\n",
        "    print(\"constant\")\n",
        "else:\n",
        "    print(\"balanced\")  # anything other than all zeroes means the function is balanced."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c601a252-d1d4-4def-b9a4-d05494d34899",
      "metadata": {},
      "source": [
        "Ci-dessus, la première ligne de la sortie est la chaîne de bits des résultats des mesures. La deuxième ligne indique si la chaîne de bits implique que la fonction était équilibrée ou constante. Si la chaîne de bits contient tous les zéros, elle est constante; sinon, elle est équilibrée. Ainsi, en exécutant une seule fois le circuit quantique ci-dessus, nous pouvons déterminer si la fonction est constante ou équilibrée!\n",
        "\n",
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Vérifiez votre compréhension\n",
        "\n",
        "Combien de requêtes faudrait-il à un ordinateur classique pour déterminer avec une certitude de 100 % si une fonction est constante ou équilibrée? Rappelez-vous que, classiquement, une requête unique ne vous permet d'appliquer la fonction qu'à une seule chaîne de bits.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Réponse\">\n",
        "    Il existe $2^n$ chaînes de bits possibles à vérifier et, dans le pire des cas, vous devrez tester $2^n/2+1$ d'entre elles. Par exemple, si la fonction est constante et que vous continuez à mesurer \"1\" comme sortie de la fonction, vous ne pouvez pas être certain qu'elle est vraiment constante avant d'avoir vérifié plus de la moitié des résultats. Auparavant, il fallait être très malchanceux pour continuer à mesurer \"1\" sur une fonction équilibrée. C'est comme si l'on jouait à pile ou face et que l'on tombait à chaque fois sur pile. C'est peu probable, mais pas impossible.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Comment votre réponse ci-dessus changerait-elle si vous deviez simplement mesurer jusqu'à ce qu'un résultat (équilibré ou constant) soit plus probable que l'autre? Combien de requêtes faut-il dans ce cas?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Réponse\">\n",
        "    Dans ce cas, il suffit de mesurer deux fois. Si les deux mesures sont différentes, vous savez que la fonction est équilibrée. Si les deux mesures sont identiques, il peut s'agir d'un équilibre ou d'une constance. La probabilité qu'il soit équilibré avec cet ensemble de mesures est : $\\frac{1}{2}\\frac{2^n /2 - 1}{2^n-1}$. Cette probabilité est inférieure à 1/2, il est donc plus probable que la fonction soit constante dans ce cas.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Ainsi, l'algorithme de Deutsch-Jozsa a démontré une accélération exponentielle par rapport à un algorithme classique *déterministe* (qui renvoie la réponse avec une certitude de 100 %), mais aucune accélération significative par rapport à un algorithme *probabiliste* (qui renvoie un résultat *susceptible d'* être la bonne réponse).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "37d8d1b5-1593-480e-afb9-cdae1debb8ea",
      "metadata": {},
      "source": [
        "<span id=\"the-bernstein---vazirani-problem\" />\n",
        "\n",
        "### Le problème de Bernstein-Vazirani\n",
        "\n",
        "En 1997, Ethan Bernstein et Umesh Vazirani ont utilisé l'algorithme de Deutsch-Jozsa pour résoudre un problème plus spécifique et plus restreint que le problème de Deutsch-Jozsa. Plutôt que d'essayer simplement de faire la distinction entre deux classes différentes de fonctions, comme dans le cas D-J, Bernstein et Vazirani ont utilisé l'algorithme Deutsch-Jozsa pour apprendre une chaîne de caractères codée dans une fonction. Voici le problème :\n",
        "\n",
        "La fonction $f:\\{0,1\\}^n \\rightarrow \\{0,1\\}$ prend toujours une chaîne de $n$ bits et produit un seul bit. Mais maintenant, au lieu de promettre que la fonction est équilibrée ou constante, on nous promet que la fonction est le produit de points entre la chaîne d'entrée $x$ et une chaîne secrète $n$ -bit $s$, modulo 2. (Ce produit de points modulo 2 est appelé \"produit de points binaires\") Le problème est de découvrir ce qu'est la chaîne secrète, $n$ -bit.\n",
        "\n",
        "En d'autres termes, on nous donne une fonction de boîte noire $f: {0,1}^n \\rightarrow {0,1}$ qui satisfait $f(x) = s \\cdot x$ pour une certaine chaîne $s$, et nous voulons apprendre la chaîne $s$.\n",
        "\n",
        "Voyons comment l'algorithme D-J résout ce problème :\n",
        "\n",
        "1. Tout d'abord, une porte de Hadamard est appliquée aux qubits d'entrée $n$, et une porte NOT plus une porte de Hadamard sont appliquées au qubit de sortie, ce qui crée l'état :\n",
        "\n",
        "$$\n",
        "|\\Psi\\rangle = |-\\rangle_{n} \\otimes |+\\rangle_{n-1} \\otimes |+\\rangle_{n-2} \\otimes ... \\otimes |+\\rangle_0\n",
        "$$\n",
        "\n",
        "L'état des qubits 1 à $n$ peut être écrit plus simplement comme une somme sur tous $2^n$ les états de base des qubits $n$ $|00...00\\rangle, |00...01\\rangle, |000...11\\rangle, ..., |111...11\\rangle$. Nous appelons l'ensemble de ces états de base $\\Sigma^n$. (Voir [Fundamentals of Quantum Algorithms](/learning/courses/fundamentals-of-quantum-algorithms/quantum-query-algorithms/deutsch-jozsa-algorithm) pour plus de détails)\n",
        "\n",
        "$$\n",
        "|\\Psi\\rangle = |-\\rangle \\otimes \\frac{1}{\\sqrt{2^n}}\\sum\\limits_{x \\in \\Sigma^n}{|x\\rangle}\n",
        "$$\n",
        "\n",
        "2. Ensuite, la porte $U_f$ est appliquée aux qubits. Cette porte prend les n premiers qubits en entrée (qui sont maintenant dans une superposition égale de toutes les chaînes de n bits possibles) et applique la fonction $f(x)=s \\cdot x$ au qubit de sortie, de sorte que ce qubit est maintenant dans l'état : $ |- \\oplus f(x)\\rangle$. Grâce au mécanisme de rebond de phase, l'état de ce qubit reste inchangé, mais certains des termes de l'état du qubit d'entrée prennent un signe moins :\n",
        "\n",
        "$$\n",
        "|\\Psi\\rangle = |-\\rangle \\otimes \\frac{1}{\\sqrt{2^n}}\\sum\\limits_{x \\in \\Sigma^n}{(-1)^{f(x)}|x\\rangle}\n",
        "$$\n",
        "\n",
        "3. La série suivante de Hadamards est appliquée aux qubits 0 à $n-1$. Dans ce cas, il peut s'avérer difficile de garder la trace des signes moins. Il est utile de savoir que l'application d'une couche de Hadamards à $n$ qubits dans un état de base standard $|x\\rangle$ peut s'écrire comme suit :\n",
        "\n",
        "$$\n",
        "H^{\\otimes n} |x\\rangle = \\frac{1}{\\sqrt{2^n}}\\sum\\limits_{y \\in \\Sigma^n}{(-1)^{x \\cdot y}|y\\rangle}\n",
        "$$\n",
        "\n",
        "L'état devient donc :\n",
        "\n",
        "$$\n",
        "|\\Psi\\rangle = |-\\rangle \\otimes \\frac{1}{2^n}\\sum\\limits_{x \\in \\Sigma^n}\\sum\\limits_{y \\in \\Sigma^n}{(-1)^{(s \\cdot x) + (x \\cdot y)}|y\\rangle}\n",
        "$$\n",
        "\n",
        "4. L'étape suivante consiste à mesurer les premiers $n$ bits. Mais qu'allons-nous mesurer? Il s'avère que l'état ci-dessus se simplifie en : $|\\Psi\\rangle = |-\\rangle \\otimes |s\\rangle$ mais c'est loin d'être évident. Si vous souhaitez vous familiariser avec les mathématiques, consultez le cours [Fundamentals of Quantum Algorithms](/learning/courses/fundamentals-of-quantum-algorithms/quantum-query-algorithms/deutsch-jozsa-algorithm#the-bernstein-vazirani-problem) de John Watrous. Le fait est que le mécanisme de rebond de phase conduit à ce que les qubits d'entrée soient dans l'état $|s\\rangle$. Ainsi, pour découvrir quelle était la chaîne secrète $s$, il suffit de mesurer les qubits!\n",
        "\n",
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Vérifiez votre compréhension\n",
        "\n",
        "Vérifier que l'état de l'étape 3 ci-dessus est bien l'état $|s\\rangle$ pour le cas particulier de $n=1$.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Réponse\">\n",
        "    Lorsque vous écrivez explicitement les deux sommations, vous devriez obtenir un état à quatre termes (omettons pour cela l'état de sortie $|-\\rangle$ ) :\n",
        "\n",
        "    $$\n",
        "    |\\Psi\\rangle = \\frac{1}{2}[|0\\rangle + (-1)^s |0\\rangle + |1\\rangle + (-1)^{(s+1)} |1\\rangle]\n",
        "    $$\n",
        "\n",
        "    Si $s=0$, les deux premiers termes s'additionnent de manière constructive et les deux derniers s'annulent, ce qui nous donne $|\\Psi\\rangle = |0\\rangle$. Si $s=1$, les deux derniers termes s'additionnent de manière constructive et les deux premiers s'annulent, ce qui nous donne $|\\Psi\\rangle = |1\\rangle$. Donc, dans les deux cas, $|\\Psi\\rangle = |s\\rangle$. Nous espérons que ce cas le plus simple vous donne une idée de la manière dont fonctionne le cas général avec $n$ qubits : tous les termes qui ne sont pas $|s\\rangle$ interfèrent, laissant seulement l'état $|s\\rangle$.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Comment le même algorithme peut-il résoudre les problèmes de Bernstein-Vazirani et de Deutsch-Jozsa? Pour comprendre cela, pensez aux fonctions de Bernstein-Vazirani, qui sont de la forme $f(x) = s \\cdot x$. Ces fonctions sont-elles également des fonctions de Deutsch-Jozsa? Il s'agit de déterminer si les fonctions de cette forme satisfont à la promesse du problème de Deutsch-Jozsa, à savoir qu'elles sont soit *constantes*, soit *équilibrées*. Comment cela nous aide-t-il à comprendre comment un même algorithme résout deux problèmes différents?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Réponse\">\n",
        "    Toute fonction de Bernstein-Vazirani de la forme $f(x) = s \\cdot x$ satisfait également la promesse du problème de Deutsch-Jozsa : si s=00...00, alors la fonction est constante (elle renvoie toujours 0 pour chaque chaîne de caractères x). Si s est une autre chaîne, la fonction est équilibrée. Ainsi, l'application de l'algorithme de Deutsch-Jozsa à l'une de ces fonctions résout simultanément les deux problèmes! Il renvoie la chaîne, et si cette chaîne est 00...00, nous savons qu'elle est constante; s'il y a au moins un \"1\" dans la chaîne, nous savons qu'elle est équilibrée.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Nous pouvons également vérifier que cet algorithme résout avec succès le problème de Bernstein-Vazirani en le testant expérimentalement. Tout d'abord, nous créons la fonction B-V qui vit à l'intérieur de la boîte noire :\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "45449a26-0bd0-4244-87be-3309937955b9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/deutsch-jozsa/extracted-outputs/45449a26-0bd0-4244-87be-3309937955b9-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Step 1: Map the problem\n",
        "\n",
        "\n",
        "def bv_function(s):\n",
        "    \"\"\"\n",
        "    Create a Bernstein-Vazirani function from a string of 1s and 0s.\n",
        "    \"\"\"\n",
        "    qc = QuantumCircuit(len(s) + 1)\n",
        "    for index, bit in enumerate(reversed(s)):\n",
        "        if bit == \"1\":\n",
        "            qc.cx(index, len(s))\n",
        "    return qc\n",
        "\n",
        "\n",
        "display(bv_function(\"1000\").draw(\"mpl\"))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "0cf6f2bc-3b5e-46d2-ab82-1a190e77c42b",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/deutsch-jozsa/extracted-outputs/0cf6f2bc-3b5e-46d2-ab82-1a190e77c42b-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 18,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "string = \"1000\"  # secret string that we'll pretend we don't know or have access to\n",
        "n = len(string)\n",
        "\n",
        "qc = QuantumCircuit(n + 1, n)\n",
        "qc.x(n)\n",
        "qc.h(range(n + 1))\n",
        "qc.barrier()\n",
        "# qc.compose(oracle, inplace = True)\n",
        "qc.compose(bv_function(string), inplace=True)\n",
        "qc.barrier()\n",
        "qc.h(range(n))\n",
        "qc.measure(range(n), range(n))\n",
        "\n",
        "qc.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "5d225a6e-e3d0-4c08-8aeb-f03337bfffc4",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 2: Transpile\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "\n",
        "qc_isa = pm.run(qc)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8fef6a65-227a-4f27-af3e-348513e1cd33",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 3: Run the job on a real quantum computer\n",
        "\n",
        "job = sampler.run([qc_isa], shots=1)\n",
        "# job = sampler_sim.run([qc_isa],shots=1) # uncomment this line to run on simulator instead\n",
        "res = job.result()\n",
        "counts = res[0].data.c.get_counts()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "ec576787-d9ba-4406-b799-9c0de21a8088",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "{'0000': 1}\n"
          ]
        }
      ],
      "source": [
        "# Step 4: Visualize and analyze results\n",
        "\n",
        "## Analysis\n",
        "print(counts)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "46ff0418-570d-4e78-be36-f403aeccc392",
      "metadata": {},
      "source": [
        "Ainsi, avec une seule requête, l'algorithme de Deutsch-Jozsa renverra la chaîne $s$ utilisée dans la fonction : $f(x)=x \\cdot s$ lorsque nous l'appliquerons au problème de Bernstein-Vazirani. Avec un algorithme classique, il faudrait $n$ requêtes pour résoudre le même problème.\n",
        "\n",
        "<span id=\"conclusion\" />\n",
        "\n",
        "## Conclusion\n",
        "\n",
        "Nous espérons qu'en examinant ces exemples simples, nous vous avons donné une meilleure idée de la manière dont les ordinateurs quantiques sont capables d'exploiter la superposition, l'intrication et l'interférence pour atteindre leur puissance par rapport aux ordinateurs classiques.\n",
        "\n",
        "L'algorithme de Deutsch-Jozsa revêt une importance historique considérable, car il a été le premier à démontrer une accélération par rapport à un algorithme classique, mais il ne s'agissait que d'une accélération polynomiale. L'algorithme Deutsch-Jozsa n'est que le début de l'histoire.\n",
        "\n",
        "Après avoir utilisé l'algorithme pour résoudre leur problème, Bernstein et Vazirani s'en sont servis comme base pour un problème récursif plus compliqué, appelé *problème récursif d'échantillonnage de Fourier*. Leur solution offre une accélération super-polynomiale par rapport aux algorithmes classiques. Avant même Bernstein et Vazirani, Peter Shor avait déjà mis au point son célèbre algorithme qui permettait aux ordinateurs quantiques de factoriser de grands nombres exponentiellement plus vite que n'importe quel algorithme classique. Ces résultats, pris dans leur ensemble, ont montré la promesse excitante d'un futur ordinateur quantique et ont incité les physiciens et les ingénieurs à faire de cet avenir une réalité.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c76273ac-ad3c-4c82-94e8-213e887dc7b7",
      "metadata": {},
      "source": [
        "<span id=\"questions\" />\n",
        "\n",
        "## Questions\n",
        "\n",
        "Les enseignants peuvent demander des versions de ces cahiers avec les corrigés et des conseils sur le placement dans les programmes d'études communs en répondant à cette [enquête rapide](https://ibm.biz/classrooms_instructor_key_request) sur la façon dont les cahiers sont utilisés.\n",
        "\n",
        "<span id=\"critical-concepts\" />\n",
        "\n",
        "### Concepts essentiels\n",
        "\n",
        "* les algorithmes Deutsch et Deutsch-Jozsa utilisent le parallélisme quantique combiné à l'interférence pour trouver une réponse à un problème plus rapidement qu'un ordinateur classique.\n",
        "* le mécanisme de rebond de phase est un phénomène quantique contre-intuitif qui transfère des opérations sur un qubit à la phase d'un autre qubit. Les algorithmes de Deutsch et de Deutsch-Jozsa utilisent ce mécanisme.\n",
        "* L'algorithme de Deutsch-Jozsa offre une accélération polynomiale par rapport à n'importe quel algorithme classique déterministe.\n",
        "* L'algorithme de Deutsch-Jozsa peut être appliqué à un autre problème, appelé problème de Bernstein-Vazirani, qui consiste à trouver une chaîne cachée codée dans une fonction.\n",
        "\n",
        "<span id=\"true/false\" />\n",
        "\n",
        "### true/false\n",
        "\n",
        "1. T/F L'algorithme de Deutsch est un cas particulier de l'algorithme de Deutsch-Jozsa où l'entrée est un seul qubit.\n",
        "2. T/F Les algorithmes de Deutsch et de Deutsch-Jozsa utilisent la superposition et l'interférence quantiques pour atteindre leur efficacité.\n",
        "3. T/F L'algorithme de Deutsch-Jozsa nécessite plusieurs évaluations de fonctions pour déterminer si une fonction est constante ou équilibrée.\n",
        "4. T/F L'\"algorithme de Bernstein-Vazirani\" est en fait le même que l'algorithme de Deutsch-Jozsa, appliqué à un problème différent.\n",
        "5. T/F L'algorithme de Bernstein-Vazirani peut trouver plusieurs chaînes secrètes simultanément.\n",
        "\n",
        "<span id=\"short-answer\" />\n",
        "\n",
        "### Réponse courte\n",
        "\n",
        "1. Combien de temps faudrait-il à un algorithme classique pour résoudre le problème Deutsch-Jozsa dans le pire des cas?\n",
        "\n",
        "2. Combien de temps faudrait-il à un algorithme classique pour résoudre le problème de Bernstein-Vazirani? Quel est le gain de vitesse offert par l'algorithme DJ dans ce cas?\n",
        "\n",
        "3. Décrire le mécanisme de rétroaction en phase et son fonctionnement pour résoudre les problèmes de Deutsch-Jozsa et de Bernstein-Vazirani.\n",
        "\n",
        "<span id=\"challenge-problem\" />\n",
        "\n",
        "### Problème difficile\n",
        "\n",
        "1. L'algorithme de Deutsch-Jozsa : Rappelez-vous que vous aviez une question ci-dessus vous demandant de calculer les états intermédiaires des qubits $\\pi_1$, et $\\pi_2$ de l'algorithme de Deutsch. Faites de même pour les états intermédiaires $n+1$ -qubit $\\pi_1$, et $\\pi_2$ de l'algorithme de Deutsch-Jozsa, pour le cas spécifique où $n=2$. Ensuite, vérifiez que $\\pi_3 = |-\\rangle \\otimes \\sum\\limits_{x_0...x_n}(-1)^{f(x_0...x_n)}|x_0 ... x_n\\rangle$, à nouveau, pour le cas spécifique où $n=2$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "in_page_toc_max_heading_level": 2,
    "in_page_toc_min_heading_level": 2,
    "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"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "state": {},
        "version_major": 2,
        "version_minor": 0
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}