{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "9857bace",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Circuits dynamiques de référence avec paires de cloches coupées\"\n",
        "description: \"Évaluer les capacités des circuits dynamiques à l'aide du protocole Cut Bell Pair\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore LOCC Ristè bitstr textcoords xytext fontsize Carrera */}\n",
        "\n",
        "<span id=\"benchmark-dynamic-circuits-with-cut-bell-pairs\" />\n",
        "\n",
        "# Circuits dynamiques de référence avec paires de cloches coupées\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d08a5e51",
      "metadata": {},
      "source": [
        "*Estimation de l'utilisation : 22 secondes sur un processeur r2 Heron (REMARQUE : il s'agit uniquement d'une estimation. Votre durée d'exécution peut varier.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0b9b92c8",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Résultats d'apprentissage\n",
        "\n",
        "À l'issue de ce tutoriel, les utilisateurs devraient être en mesure de :\n",
        "\n",
        "* Comment construire des circuits dynamiques avec des mesures en cours de circuit et un système classique de transmission directe pour téléporter l'intrication entre des qubits distants;\n",
        "* Comment calculer et interpréter la mesure d'erreur permettant de quantifier la fidélité de la paire de Bell sur un appareil;\n",
        "* Comment déterminer, à partir des résultats des tests de performance, quelles paires de qubits sont les mieux adaptées aux opérations basées sur le LOCC.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bff9db42",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prérequis\n",
        "\n",
        "Nous recommandons aux utilisateurs de se familiariser avec les sujets suivants avant de suivre ce tutoriel :\n",
        "\n",
        "* [Les concepts fondamentaux de l'informatique quantique](/learning/courses/basics-of-quantum-information), notamment les états de Bell, l'intrication et les portes quantiques;\n",
        "* Connaissance des [circuits dynamiques](/docs/guides/classical-feedforward-and-control-flow) ( [mesures en cours de fonctionnement](/docs/guides/execute-dynamic-circuits#mid-circuit-measurements) et régulation classique à action directe);\n",
        "* Connaissances de base sur [Qiskit SDK](/docs/guides) et [IBM Quantum® Compute Service](/docs/guides/compute-services#qiskit-runtime), ainsi qu’un accès à un [compte IBM Quantum](/docs/guides/cloud-setup).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "77dca78b",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Arrière-plan\n",
        "\n",
        "Le matériel quantique est généralement limité aux interactions locales, mais de nombreux algorithmes nécessitent l'intrication de qubits distants, voire [de qubits situés sur des processeurs distincts](#references). Les circuits dynamiques, c'est-à-dire les circuits avec mesure à mi-circuit et feedforward, permettent de surmonter ces limites en utilisant la communication classique en temps réel pour mettre en œuvre efficacement des opérations quantiques non locales. Dans cette approche, les résultats de mesure provenant d'une partie d'un circuit (ou d'un QPU) peuvent déclencher de manière conditionnelle des portes logiques sur une autre partie, ce qui nous permet de téléporter l'intrication sur de longues distances. Cela constitue la base des schémas **LOCC (Local Operations and Classical Communication)**, dans lesquels nous consommons des états de ressources intriqués (paires de Bell) et communiquons les résultats des mesures de manière classique afin de relier des qubits distants.\n",
        "\n",
        "Une utilisation prometteuse du LOCC consiste à réaliser des portes CNOT virtuelles à longue portée par téléportation, comme le montre [le tutoriel](/docs/tutorials/long-range-entanglement) sur l'intrication à longue portée. Au lieu d'un CNOT direct à longue portée (que la connectivité matérielle pourrait ne pas permettre), nous créons des paires de Bell et mettons en œuvre une porte basée sur la téléportation. Cependant, la fidélité de ces opérations dépend des caractéristiques matérielles. La décohérence des qubits pendant le délai nécessaire (en attendant les résultats des mesures) et la latence de la communication classique peuvent dégrader l'état intriqué. De plus, les erreurs sur les mesures à mi-circuit sont plus difficiles à corriger que les erreurs sur les mesures finales, car elles se propagent au reste du circuit par le biais des portes conditionnelles.\n",
        "\n",
        "Dans [l](#references) 'expérience de référence, les auteurs proposent un critère de fidélité pour les paires de Bell afin de déterminer quelles parties d'un dispositif se prêtent le mieux à l'intrication basée sur la technique LOCC. L'idée est d'exécuter un petit circuit dynamique sur chaque groupe de quatre qubits connectés au sein du processeur. Ce circuit à quatre qubits commence par créer une paire de Bell sur les deux qubits centraux, puis utilise celle-ci comme ressource pour intriquer les deux qubits périphériques à l'aide de la technique LOCC. Concrètement, les qubits 1 et 2 sont préparés localement sous la forme d'une paire de Bell *non coupée* (une paire de Bell créée directement à l'aide d'une matrice de Hadamard et d'une porte CNOT, sans téléportation), puis une routine de téléportation utilise cette paire de Bell pour entrelacer les qubits 0 et 3. Les qubits 1 et 2 sont mesurés pendant l'exécution du circuit, et en fonction de ces résultats, des corrections de Pauli (un X sur le qubit 3 et un Z sur le qubit 0) sont appliquées. Les qubits 0 et 3 se retrouvent alors dans un état de Bell à la fin du circuit.\n",
        "\n",
        "Pour quantifier la qualité de cette paire intriquée finale, nous mesurons ses stabilisateurs : plus précisément, la parité dans la $Z$ base ( $Z_0Z_3$ ) et dans la $X$ base ( $X_0X_3$ ). Pour une paire de Bell parfaite, ces deux attentes sont égales à +1. En pratique, le bruit matériel réduira ces valeurs. Nous répétons donc le circuit deux fois pour chaque paire de qubits : un circuit mesure les qubits 0 et 3 dans la $Z$ base, et un autre les mesure dans la $X$ base. À partir des résultats, nous obtenons une estimation de $\\langle Z_0Z_3\\rangle$ et $\\langle X_0X_3\\rangle$ pour cette paire de qubits. Nous utilisons l'erreur quadratique moyenne (MSE) de ces stabilisateurs par rapport à la valeur idéale (1) comme mesure simple de la fidélité de l'intrication. Une MSE plus faible signifie que les deux qubits ont atteint un état de Bell plus proche de l'idéal (fidélité plus élevée), tandis qu'une MSE plus élevée indique davantage d'erreurs. En analysant cette expérience sur l'ensemble du dispositif, nous pouvons évaluer les capacités de mesure et de feedforward de différents groupes de qubits et identifier les meilleures paires de qubits pour les opérations LOCC.\n",
        "\n",
        "Ce tutoriel présente une expérience réalisée sur un dispositif « IBM Quantum® » afin d'illustrer comment des circuits dynamiques peuvent être utilisés pour générer et évaluer l'intrication entre des qubits distants. Nous allons cartographier toutes les chaînes linéaires à quatre qubits présentes sur le dispositif, exécuter le circuit de téléportation sur chacune d'entre elles, puis visualiser la distribution des valeurs MSE. Cette procédure complète explique comment tirer parti du service de calcul « IBM Quantum » et des fonctionnalités de circuits dynamiques afin d'orienter les choix liés au matériel pour la conception de circuits ou la répartition d'algorithmes quantiques au sein d'un système modulaire.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "152c479f",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Exigences\n",
        "\n",
        "Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :\n",
        "\n",
        "* Qiskit SDK v2.0 ou plus tard, avec prise en charge de [la visualisation](/docs/api/qiskit/visualization)\n",
        "* v0.40Qiskit Runtime ou plus tard (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer v0.17 ou version ultérieure (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e67f9466",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuration\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b59e4534",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "\n",
        "def create_bell_stab(initial_layouts):\n",
        "    \"\"\"\n",
        "    Create a circuit for a 1D chain of qubits (number\n",
        "    of qubits must be a multiple of 4), where a middle\n",
        "    Bell pair is consumed to create a Bell at the edge.\n",
        "    Takes as input a list of lists, where each element\n",
        "    of the list is a 1D chain of physical qubits that is\n",
        "    used as the initial_layout for the transpiled circuit.\n",
        "    Returns a list of length-2 tuples, each tuple\n",
        "    contains a circuit to measure the ZZ stabilizer and\n",
        "    a circuit to measure the XX stabilizer of the edge\n",
        "    Bell state.\n",
        "    \"\"\"\n",
        "    bell_circuits = []\n",
        "    for (\n",
        "        initial_layout\n",
        "    ) in initial_layouts:  # Iterate over chains of physical qubits\n",
        "        assert (\n",
        "            len(initial_layout) % 4 == 0\n",
        "        ), \"The length of the chain must be a multiple of 4, \"\n",
        "        f\"len(inital_layout)={len(initial_layout)}\"\n",
        "        num_pairs = len(initial_layout) // 4\n",
        "\n",
        "        bell_parallel = QuantumCircuit(4 * num_pairs, 4 * num_pairs)\n",
        "\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (c0, c1) = pair_idx * 4, pair_idx * 4 + 3  # edge qubits\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "\n",
        "            bell_parallel.h(q0)\n",
        "            bell_parallel.h(q1)\n",
        "            bell_parallel.cx(q1, q2)\n",
        "            bell_parallel.cx(q0, q1)\n",
        "            bell_parallel.cx(q2, q3)\n",
        "            bell_parallel.h(q2)\n",
        "\n",
        "        # add barrier BEFORE measurements and add id in conditional\n",
        "        bell_parallel.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "\n",
        "            bell_parallel.measure(q1, ca0)\n",
        "            bell_parallel.measure(q2, ca1)\n",
        "        # bell_parallel.barrier() #remove barrier after measurement\n",
        "\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "            with bell_parallel.if_test((ca0, 1)):\n",
        "                bell_parallel.x(q3)\n",
        "            with bell_parallel.if_test((ca1, 1)):\n",
        "                bell_parallel.z(q0)\n",
        "                bell_parallel.id(q0)  # add id here for correct alignment\n",
        "\n",
        "        bell_zz = bell_parallel.copy()\n",
        "        bell_zz.barrier()\n",
        "        bell_xx = bell_parallel.copy()\n",
        "        bell_xx.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            bell_xx.h(q0)\n",
        "            bell_xx.h(q3)\n",
        "        bell_xx.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (c0, c1) = pair_idx * 4, pair_idx * 4 + 3  # edge qubits\n",
        "\n",
        "            bell_zz.measure(q0, c0)\n",
        "            bell_zz.measure(q3, c1)\n",
        "\n",
        "            bell_xx.measure(q0, c0)\n",
        "            bell_xx.measure(q3, c1)\n",
        "\n",
        "        bell_circuits.append(bell_zz)\n",
        "        bell_circuits.append(bell_xx)\n",
        "\n",
        "    return bell_circuits\n",
        "\n",
        "\n",
        "def get_mse(result, initial_layouts):\n",
        "    \"\"\"\n",
        "    given a result object and the initial layouts,\n",
        "    returns a dict of layouts and their mse\n",
        "    \"\"\"\n",
        "    layout_mse = {}\n",
        "    for layout_idx, initial_layout in enumerate(initial_layouts):\n",
        "        layout_mse[tuple(initial_layout)] = {}\n",
        "\n",
        "        num_pairs = len(initial_layout) // 4\n",
        "\n",
        "        counts_zz = result[2 * layout_idx].data.c.get_counts()\n",
        "        total_shots = sum(counts_zz.values())\n",
        "\n",
        "        # Get ZZ expectation value\n",
        "        exp_zz_list = []\n",
        "        for pair_idx in range(num_pairs):\n",
        "            exp_zz = 0\n",
        "            for bitstr, shots in counts_zz.items():\n",
        "                bitstr = bitstr[::-1]  # reverse order to big endian\n",
        "                b1, b0 = (\n",
        "                    bitstr[pair_idx * 4],\n",
        "                    bitstr[pair_idx * 4 + 3],\n",
        "                )  # parse bitstring to get edge measurements for each 4-q chain\n",
        "                z_val0 = 1 if b0 == \"0\" else -1\n",
        "                z_val1 = 1 if b1 == \"0\" else -1\n",
        "                exp_zz += z_val0 * z_val1 * shots\n",
        "            exp_zz /= total_shots\n",
        "            exp_zz_list.append(exp_zz)\n",
        "\n",
        "        counts_xx = result[2 * layout_idx + 1].data.c.get_counts()\n",
        "        total_shots = sum(counts_xx.values())\n",
        "\n",
        "        # Get XX expectation value\n",
        "        exp_xx_list = []\n",
        "        for pair_idx in range(num_pairs):\n",
        "            exp_xx = 0\n",
        "            for bitstr, shots in counts_xx.items():\n",
        "                bitstr = bitstr[::-1]  # reverse order to big endian\n",
        "                b1, b0 = (\n",
        "                    bitstr[pair_idx * 4],\n",
        "                    bitstr[pair_idx * 4 + 3],\n",
        "                )  # parse bitstring to get edge measurements for each 4-q chain\n",
        "                x_val0 = 1 if b0 == \"0\" else -1\n",
        "                x_val1 = 1 if b1 == \"0\" else -1\n",
        "                exp_xx += x_val0 * x_val1 * shots\n",
        "            exp_xx /= total_shots\n",
        "            exp_xx_list.append(exp_xx)\n",
        "\n",
        "        mse_list = [\n",
        "            ((exp_zz - 1) ** 2 + (exp_xx - 1) ** 2) / 2\n",
        "            for exp_zz, exp_xx in zip(exp_zz_list, exp_xx_list)\n",
        "        ]\n",
        "\n",
        "        print(f\"layout {initial_layout}\")\n",
        "        for idx in range(num_pairs):\n",
        "            layout_mse[tuple(initial_layout)][\n",
        "                tuple(initial_layout[4 * idx : 4 * idx + 4])\n",
        "            ] = mse_list[idx]\n",
        "            print(\n",
        "                f\"qubits: {initial_layout[4*idx:4*idx+4]}, mse:, \"\n",
        "                f\"{round(mse_list[idx],4)}\"\n",
        "            )\n",
        "            # print(f'exp_zz: {round(exp_zz_list[idx],4)},\n",
        "            # exp_xx: {round(exp_xx_list[idx],4)}')\n",
        "        print(\" \")\n",
        "    return layout_mse\n",
        "\n",
        "\n",
        "def plot_mse_ecdfs(layouts_mse, combine_layouts=False):\n",
        "    \"\"\"\n",
        "    Plot CDF of MSE data for multiple layouts.\n",
        "    Optionally combine all data in a single CDF\n",
        "    \"\"\"\n",
        "\n",
        "    if not combine_layouts:\n",
        "        for initial_layout, layouts in layouts_mse.items():\n",
        "            sorted_layouts = dict(\n",
        "                sorted(layouts.items(), key=lambda item: item[1])\n",
        "            )  # sort layouts by mse\n",
        "\n",
        "            # get layouts and mses\n",
        "            layout_list = list(sorted_layouts.keys())\n",
        "            mse_list = np.asarray(list(sorted_layouts.values()))\n",
        "\n",
        "            # convert to numpy\n",
        "            x = np.array(mse_list)\n",
        "            y = np.arange(1, len(x) + 1) / len(x)\n",
        "\n",
        "            # Prepend (x[0], 0) to start CDF at zero\n",
        "            x = np.insert(x, 0, x[0])\n",
        "            y = np.insert(y, 0, 0)\n",
        "\n",
        "            # Create the plot\n",
        "            plt.plot(\n",
        "                x,\n",
        "                y,\n",
        "                marker=\"x\",\n",
        "                linestyle=\"-\",\n",
        "                label=f\"qubits: {initial_layout}\",\n",
        "            )\n",
        "\n",
        "            # add qubits labels for the edge pairs\n",
        "            for xi, yi, q in zip(x[1:], y[1:], layout_list):\n",
        "                plt.annotate(\n",
        "                    [q[0], q[3]],\n",
        "                    (xi, yi),\n",
        "                    textcoords=\"offset points\",\n",
        "                    xytext=(5, -10),\n",
        "                    ha=\"left\",\n",
        "                    fontsize=8,\n",
        "                )\n",
        "\n",
        "    elif combine_layouts:\n",
        "        all_layouts = {}\n",
        "        all_initial_layout = []\n",
        "        for (\n",
        "            initial_layout,\n",
        "            layouts,\n",
        "        ) in layouts_mse.items():  # puts together all layout information\n",
        "            all_layouts.update(layouts)\n",
        "            all_initial_layout += initial_layout\n",
        "\n",
        "        sorted_layouts = dict(\n",
        "            sorted(all_layouts.items(), key=lambda item: item[1])\n",
        "        )  # sort layouts by mse\n",
        "\n",
        "        # get layouts and mses\n",
        "        layout_list = list(sorted_layouts.keys())\n",
        "        mse_list = np.asarray(list(sorted_layouts.values()))\n",
        "\n",
        "        # convert to numpy\n",
        "        x = np.array(mse_list)\n",
        "        y = np.arange(1, len(x) + 1) / len(x)\n",
        "\n",
        "        # Prepend (x[0], 0) to start CDF at zero\n",
        "        x = np.insert(x, 0, x[0])\n",
        "        y = np.insert(y, 0, 0)\n",
        "\n",
        "        # Create the plot\n",
        "        plt.plot(\n",
        "            x,\n",
        "            y,\n",
        "            marker=\"x\",\n",
        "            linestyle=\"-\",\n",
        "            label=f\"qubits: {sorted(list(set(all_initial_layout)))}\",\n",
        "        )\n",
        "\n",
        "        # add qubit labels for the edge pairs\n",
        "        for xi, yi, q in zip(x[1:], y[1:], layout_list):\n",
        "            plt.annotate(\n",
        "                [q[0], q[3]],\n",
        "                (xi, yi),\n",
        "                textcoords=\"offset points\",\n",
        "                xytext=(5, -10),\n",
        "                ha=\"left\",\n",
        "                fontsize=8,\n",
        "            )\n",
        "\n",
        "    plt.xscale(\"log\")\n",
        "    plt.xlabel(\"Mean squared error of ⟨ZZ⟩ and ⟨XX⟩\")\n",
        "    plt.ylabel(\"Cumulative distribution function\")\n",
        "    plt.title(\"CDF for different initial layouts\")\n",
        "    plt.grid(alpha=0.3)\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "30e65223",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemple de simulateur à petite échelle\n",
        "\n",
        "Avant de lancer l'exécution sur le véritable QPU, nous vérifions que le circuit produit bien une paire de Bell en le testant sur un simulateur sans bruit avec une chaîne de quatre qubits `[0, 1, 2, 3]`. Nous utilisons l' IBM Quantum `Sampler` avec comme `AerSimulator` mode de backend pour exécuter les circuits.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2a16f346",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique\n",
        "\n",
        "La première étape consiste à créer un ensemble de circuits quantiques afin d'évaluer toutes les liaisons de paires de Bell candidates, adaptées à la topologie du dispositif. `[0, 1, 2, 3]`Nous commençons par construire de tels circuits à partir d'une chaîne de quatre qubits.\n",
        "\n",
        "La `create_bell_stab()` routine effectue les opérations suivantes pour chaque chaîne :\n",
        "\n",
        "* Préparez une paire de Bell intermédiaire : appliquez un Hadamard sur le qubit 1 et un CNOT du qubit 1 au qubit 2. Cela entremêle les qubits 1 et 2 (créant un état $|\\Phi^+\\rangle = (|00\\rangle + |11\\rangle)/\\sqrt{2}$ de Bell).\n",
        "* Entrelacer les qubits périphériques : appliquer un CNOT du qubit 0 au qubit 1, et un CNOT du qubit 2 au qubit 3. Cela relie les paires initialement séparées afin que les qubits 0 et 3 deviennent intriqués après les étapes suivantes. Un Hadamard sur le qubit 2 est également appliqué (celui-ci, combiné aux CNOT précédents, fait partie d'une mesure de Bell sur les qubits 1 et 2). À ce stade, les qubits 0 et 3 ne sont pas encore intriqués, mais les qubits 1 et 2 sont intriqués avec eux dans un état plus large à quatre qubits.\n",
        "* Mesures à mi-circuit et feedforward : les qubits 1 et 2 (les qubits du milieu) sont mesurés dans la base de calcul, ce qui donne deux bits classiques. Sur la base des résultats de ces mesures, nous appliquons des opérations conditionnelles : si la mesure du qubit 1 (appelons-la bit $m_{12}$ ) est 1, nous appliquons une $X$ porte sur le qubit 3; si la mesure du qubit 2 ( $m_{21}$ ) est 1, nous appliquons une $Z$ porte sur le qubit 0. Ces portes conditionnelles (réalisées à l'aide de Qiskit `if_test`/`if_else` construct) mettent en œuvre les corrections de téléportation standard. Ils « annulent » les inversions aléatoires de Pauli qui se produisent en raison de la projection des qubits 1 et 2, garantissant ainsi que les qubits 0 et 3 se retrouvent dans un état de Bell connu, quels que soient les résultats des mesures. Après cette étape, les qubits 0 et 3 devraient idéalement être intriqués dans l'état de Bell $|\\Phi^+\\rangle$.\n",
        "* Mesurer les stabilisateurs de la paire de cloches : Nous avons ensuite divisé le circuit en deux versions. Dans la première version, nous mesurons le $ZZ$ stabilisateur sur les qubits 0 et 3. Dans la deuxième version, nous mesurons le $XX$ stabilisateur sur ces qubits.\n",
        "\n",
        "Pour chaque configuration initiale à quatre qubits, la fonction ci-dessus renvoie deux circuits (un pour la mesure de stabilisation de l' $ZZ$, et un pour celle de l' $XX$ ). Ces circuits comprennent des mesures en cours d'exécution et des opérations conditionnelles (if/else), qui constituent les instructions clés du circuit dynamique.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "160e25c5",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/edc-cut-bell-pair-benchmarking/extracted-outputs/160e25c5-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 19,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "# 4-qubit chain for simulation\n",
        "sim_layout = [[0, 1, 2, 3]]\n",
        "\n",
        "aer_backend = AerSimulator()\n",
        "sim_circuits = create_bell_stab(sim_layout)\n",
        "sim_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "17953786",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique\n",
        "\n",
        "Avant d'exécuter nos circuits, nous devons les transcompiler en opérations de porte prises en charge par le backend spécifié. La transpilation permettra de mapper le circuit abstrait sur les qubits physiques et l'ensemble de portes du backend sélectionné. Comme nous avons déjà sélectionné des qubits physiques spécifiques pour chaque chaîne (en fournissant un `initial_layout` au générateur de circuits), nous utilisons le transpileur `optimization_level=0` avec cette configuration fixe. Cela indique à Qiskit de ne pas réaffecter les qubits ni d'effectuer d'optimisations lourdes susceptibles de modifier la structure du circuit. Nous souhaitons conserver l'ordre des opérations (en particulier les portes conditionnelles) exactement tel qu'il a été spécifié.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "61dd47b7",
      "metadata": {},
      "outputs": [],
      "source": [
        "pm_sim = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=aer_backend, initial_layout=sim_layout[0]\n",
        ")\n",
        "isa_sim_circuits = pm_sim.run(sim_circuits)\n",
        "isa_sim_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cd161710",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Étape 3 : Exécutez à l'aide d' Qiskit primitives\n",
        "\n",
        "Nous pouvons désormais lancer l'expérience sur le backend du simulateur sans bruit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "71750073",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "layout [0, 1, 2, 3]\n",
            "qubits: [0, 1, 2, 3], mse:, 0.0\n",
            " \n"
          ]
        }
      ],
      "source": [
        "# Run on noiseless simulator\n",
        "sampler_sim = Sampler(mode=aer_backend)\n",
        "sim_job = sampler_sim.run(isa_sim_circuits)\n",
        "sim_mse = get_mse(sim_job.result(), sim_layout)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4b0b451f",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-the-desired-classical-format\" />\n",
        "\n",
        "### Étape 4 : Post-traitement et renvoi du résultat dans le format classique souhaité\n",
        "\n",
        "La dernière étape consiste à calculer la métrique d'erreur quadratique moyenne (MSE) pour chaque groupe de qubits testé et à résumer les résultats. Pour chaque chaîne, nous avons désormais les valeurs mesurées $\\langle Z_0Z_3\\rangle$ et $\\langle X_0X_3\\rangle$. Si les qubits 0 et 3 étaient parfaitement intriqués dans un état $|\\Phi^+\\rangle$ de Bell, nous nous attendrions à ce que ces deux valeurs soient égales à +1. Nous quantifions l'écart à l'aide de l'erreur quadratique moyenne (MSE) :\n",
        "\n",
        "$\\text{MSE} = \\frac{( \\langle Z_0Z_3\\rangle - 1)^2 + (\\langle X_0X_3\\rangle - 1)^2}{2}.$\n",
        "\n",
        "Cette valeur est égale à 0 pour une paire de Bell parfaite, et augmente à mesure que l'état intriqué devient plus bruité (avec des résultats aléatoires dont l'espérance est proche de 0, l'erreur quadratique moyenne tendrait vers 1). Le code calcule cette erreur quadratique moyenne (MSE) pour chaque groupe de quatre qubits. En l'absence de bruit, on constate, comme prévu, que MSE = 0 dans cet exemple de simulateur à petite échelle.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c8e42569",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Exemple de matériel à grande échelle\n",
        "\n",
        "Nous rassemblons ici tous ces éléments au sein d'un processus unique à plus grande échelle, qui est ensuite exécuté sur du matériel quantique réel.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "55f49481",
      "metadata": {},
      "outputs": [],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(operational=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "27ee6db2",
      "metadata": {},
      "source": [
        "Nous parcourons par programmation la carte de couplage du dispositif à la recherche de toutes les chaînes de quatre qubits reliées de manière linéaire. Chacune de ces chaînes (identifiées par les indices de qubits $[q0-q1-q2-q3]$ ) sert de cas test pour le circuit d'échange d'intrication. En identifiant tous les chemins d' length-4 s possibles, nous garantissons une couverture maximale pour tous les groupements de qubits susceptibles de mettre en œuvre le protocole.\n",
        "\n",
        "Nous générons ces chaînes à l'aide d'une fonction d'aide qui effectue une recherche gloutonne sur le graphe de l'appareil. Il renvoie des « bandes » de quatre chaînes de quatre qubits regroupées en groupes de 16 qubits. Le regroupement nous permet de mener plusieurs expériences à quatre qubits en parallèle sur différentes parties de la puce et d'exploiter efficacement l'ensemble du dispositif. Chaque bande de 16 qubits contient quatre chaînes disjointes, ce qui signifie qu'aucun qubit n'est réutilisé au sein de ce groupe. Par exemple, une bande peut être constituée des chaînes $[0-1-2-3]$, $[4-5-6-7]$, $[8-9-10-11]$ et $[12-13-14-15]$, regroupées en un seul ensemble. Tout qubit qui n'a pas été inclus dans une bande est renvoyé dans la `leftover` variable.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0cae5453",
      "metadata": {},
      "outputs": [],
      "source": [
        "from itertools import chain\n",
        "from collections import defaultdict\n",
        "\n",
        "\n",
        "def stripes16_from_backend(backend):\n",
        "    \"\"\"\n",
        "    Creates stripes of 16 qubits, four non-overlapping\n",
        "    four-qubit chains, that cover as much of the coupling\n",
        "    map as possible. Returns any unused qubits as leftovers.\n",
        "    \"\"\"\n",
        "    # get the undirected adjacency list\n",
        "    edges = backend.coupling_map.get_edges()\n",
        "    graph = defaultdict(set)\n",
        "    for u, v in edges:\n",
        "        graph[u].add(v)\n",
        "        graph[v].add(u)\n",
        "\n",
        "    qubits = sorted(graph)  # all qubit indices that appear\n",
        "\n",
        "    # greedy search for 4-long linear chains (blocks) ────────────\n",
        "    used = set()  # qubits already placed in a block\n",
        "    blocks = []  # each block is a four-qubit list\n",
        "\n",
        "    for q in qubits:  # deterministic order for reproducibility\n",
        "        if q in used:\n",
        "            continue  # already consumed by earlier block\n",
        "\n",
        "        # depth-first \"straight\" walk of length 3 without revisiting nodes\n",
        "        def extend(path):\n",
        "            if len(path) == 4:\n",
        "                return path\n",
        "            tip = path[-1]\n",
        "            for nbr in sorted(graph[tip]):  # deterministic\n",
        "                if nbr not in path and nbr not in used:\n",
        "                    maybe = extend(path + [nbr])\n",
        "                    if maybe:\n",
        "                        return maybe\n",
        "            return None\n",
        "\n",
        "        block = extend([q])\n",
        "        if block:  # found a 4-node path\n",
        "            blocks.append(block)\n",
        "            used.update(block)\n",
        "\n",
        "    # bundle four four-qubit blocks into one 16-qubit\n",
        "    # stripe (max number of measurement compatible with if-else)\n",
        "    stripes = [\n",
        "        list(chain.from_iterable(blocks[i : i + 4]))\n",
        "        for i in range(0, len(blocks) // 4 * 4, 4)  # full groups of four\n",
        "    ]\n",
        "\n",
        "    leftovers = set(qubits) - set(chain.from_iterable(stripes))\n",
        "    return stripes, leftovers"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "3cf9db43",
      "metadata": {},
      "outputs": [],
      "source": [
        "initial_layouts, leftover = stripes16_from_backend(backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8121aa97",
      "metadata": {},
      "source": [
        "Nous construisons ensuite le circuit pour chaque bande de 16 qubits à l'aide de la fonction `create_bell_stab()`. À l'issue de cette étape, nous disposons d'une liste de circuits couvrant toutes les chaînes de quatre qubits du dispositif. Nous compilons ensuite les circuits et les exécutons sur le backend réel, puis nous traitons les résultats.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "bd04755f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "layout [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n",
            "qubits: [0, 1, 2, 3], mse:, 0.6302\n",
            "qubits: [4, 5, 6, 7], mse:, 0.0949\n",
            "qubits: [8, 9, 10, 11], mse:, 0.1729\n",
            "qubits: [12, 13, 14, 15], mse:, 0.0473\n",
            " \n",
            "layout [16, 23, 22, 21, 17, 27, 26, 25, 18, 31, 30, 29, 19, 35, 34, 33]\n",
            "qubits: [16, 23, 22, 21], mse:, 0.0533\n",
            "qubits: [17, 27, 26, 25], mse:, 0.2966\n",
            "qubits: [18, 31, 30, 29], mse:, 0.0447\n",
            "qubits: [19, 35, 34, 33], mse:, 0.0392\n",
            " \n",
            "layout [36, 41, 42, 43, 37, 45, 46, 47, 38, 49, 50, 51, 39, 53, 54, 55]\n",
            "qubits: [36, 41, 42, 43], mse:, 0.1577\n",
            "qubits: [37, 45, 46, 47], mse:, 0.0705\n",
            "qubits: [38, 49, 50, 51], mse:, 0.2914\n",
            "qubits: [39, 53, 54, 55], mse:, 0.1711\n",
            " \n",
            "layout [56, 63, 62, 61, 57, 67, 66, 65, 58, 71, 70, 69, 59, 75, 74, 73]\n",
            "qubits: [56, 63, 62, 61], mse:, 0.1236\n",
            "qubits: [57, 67, 66, 65], mse:, 0.9969\n",
            "qubits: [58, 71, 70, 69], mse:, 0.0631\n",
            "qubits: [59, 75, 74, 73], mse:, 0.0301\n",
            " \n",
            "layout [76, 81, 82, 83, 77, 85, 86, 87, 78, 89, 90, 91, 79, 93, 94, 95]\n",
            "qubits: [76, 81, 82, 83], mse:, 0.2787\n",
            "qubits: [77, 85, 86, 87], mse:, 0.0497\n",
            "qubits: [78, 89, 90, 91], mse:, 0.1271\n",
            "qubits: [79, 93, 94, 95], mse:, 0.0468\n",
            " \n",
            "layout [96, 103, 102, 101, 97, 107, 106, 105, 98, 111, 110, 109, 99, 115, 114, 113]\n",
            "qubits: [96, 103, 102, 101], mse:, 0.8657\n",
            "qubits: [97, 107, 106, 105], mse:, 0.0399\n",
            "qubits: [98, 111, 110, 109], mse:, 0.0667\n",
            "qubits: [99, 115, 114, 113], mse:, 0.2444\n",
            " \n",
            "layout [116, 121, 122, 123, 117, 125, 126, 127, 118, 129, 130, 131, 119, 133, 134, 135]\n",
            "qubits: [116, 121, 122, 123], mse:, 0.0429\n",
            "qubits: [117, 125, 126, 127], mse:, 0.0487\n",
            "qubits: [118, 129, 130, 131], mse:, 0.0823\n",
            "qubits: [119, 133, 134, 135], mse:, 0.0583\n",
            " \n",
            "layout [136, 143, 142, 141, 137, 147, 146, 145, 138, 151, 150, 149, 139, 155, 154, 153]\n",
            "qubits: [136, 143, 142, 141], mse:, 0.0209\n",
            "qubits: [137, 147, 146, 145], mse:, 0.0384\n",
            "qubits: [138, 151, 150, 149], mse:, 0.4941\n",
            "qubits: [139, 155, 154, 153], mse:, 0.1062\n",
            " \n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "circuits = create_bell_stab(initial_layouts)\n",
        "\n",
        "# -------------------------Step 2-------------------------\n",
        "isa_circuits = []\n",
        "for ind, init_layout in enumerate(initial_layouts):\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=0, backend=backend, initial_layout=init_layout\n",
        "    )\n",
        "    isa_circ = pm.run(circuits[ind * 2 : ind * 2 + 2])\n",
        "    isa_circuits.extend(isa_circ)\n",
        "isa_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)\n",
        "\n",
        "# -------------------------Step 3-------------------------\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.environment.job_tags = [\"TUT_BDC\"]\n",
        "job = sampler.run(isa_circuits)\n",
        "\n",
        "# -------------------------Step 4-------------------------\n",
        "layouts_mse = get_mse(job.result(), initial_layouts)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "87484abf",
      "metadata": {},
      "source": [
        "Les résultats révèlent une grande variabilité dans la qualité de l'intrication à travers le dispositif. Cela confirme les conclusions de l'article selon lesquelles la fidélité de l'état de Bell peut varier d'un ordre de grandeur selon les qubits physiques utilisés. Concrètement, cela signifie que certaines régions ou certains liens de la puce sont beaucoup plus performants que d'autres pour effectuer des mesures à mi-circuit et des opérations de feedforward. Des facteurs tels que les erreurs de lecture des qubits, leur durée de vie et la diaphonie contribuent probablement à ces différences. Par exemple, si une chaîne comprend un qubit de lecture particulièrement bruyant, la mesure à mi-circuit pourrait être peu fiable, ce qui entraînerait une mauvaise fidélité pour cette paire intriquée (MSE élevé).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5af0facd",
      "metadata": {},
      "source": [
        "Enfin, nous visualisons la performance globale en traçant la fonction de distribution cumulative (CDF) des valeurs MSE pour toutes les chaînes. Le graphique CDF montre le seuil MSE sur l'axe des x et la fraction de paires de qubits qui ont au plus ce MSE sur l'axe des y. Cette courbe commence à zéro et tend vers un à mesure que le seuil augmente pour englober tous les points de données. Une forte augmentation près d'une faible MSE indiquerait que de nombreuses paires sont très fidèles; une augmentation lente signifie que de nombreuses paires présentent des erreurs plus importantes. Nous annotons le CDF avec les identités des meilleures paires. Dans le graphique, chaque point de la CDF correspond à l'erreur quadratique moyenne (MSE) d'une chaîne de quatre qubits, et nous étiquetons le point avec la paire d'indices de qubits $[q0, q3]$ qui ont été intriqués dans cette expérience. Cela permet de repérer facilement les paires de qubits physiques les plus performantes (les points situés à l'extrême gauche de la CDF).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "678ddac9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/edc-cut-bell-pair-benchmarking/extracted-outputs/678ddac9-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "plot_mse_ecdfs(layouts_mse, combine_layouts=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba738d2b",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Etapes suivantes\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommandations\">\n",
        "  Si ce travail vous a paru intéressant, les ressources suivantes pourraient vous intéresser :\n",
        "\n",
        "  * Découvrez comment mettre en œuvre [l'intrication à longue distance à l'aide de circuits dynamiques](/docs/tutorials/long-range-entanglement)\n",
        "  * Découvrez comment simuler [l](/docs/tutorials/dc-hex-ising) 'hamiltonien d'Ising « kicked » à l'aide de circuits dynamiques\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "109cd9c3",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Références\n",
        "\n",
        "[\\[1\\] Carrera Vazquez, A., Tornow, C., Ristè, D. et al. Combiner les processeurs quantiques avec des communications classiques en temps réel. Nature 636, p. 75-79 (2024)](https://www.nature.com/articles/s41586-024-08178-2).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "state": {},
        "version_major": 2,
        "version_minor": 0
      }
    },
    "hours": 1,
    "qpuSeconds": 22
  },
  "nbformat": 4,
  "nbformat_minor": 5
}