{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "1ed79d34-1831-47cb-9985-5bb7dacc70e0",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Optimisation de la transpilation avec SABRE\"\n",
        "description: \"Optimiser les circuits quantiques à l'aide de la disposition et du routage SABRE en vue d'une exécution matérielle à grande échelle.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore ylabel xlabel fontsize sharex edgecolor, fontweight, elinewidth, ecolor, prerouter, ylim */}\n",
        "\n",
        "<span id=\"transpilation-optimization-with-sabre\" />\n",
        "\n",
        "# Optimisation de la transpilation avec SABRE\n",
        "\n",
        "*Estimation du temps d'exécution : 1 minute sur un processeur Heron r2 (REMARQUE : il s'agit uniquement d'une estimation. (La durée d'exécution peut varier.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d6834016-1525-42d7-aa21-0fef9d957ecd",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Acquis d'apprentissage\n",
        "\n",
        "À l'issue de ce tutoriel, vous devriez avoir compris :\n",
        "\n",
        "* Comment configurer les paramètres SABRE (`layout_trials`, `swap_trials`, `max_iterations`) pour améliorer la qualité de la transpilation\n",
        "* Les compromis entre le temps d'exécution de la transpilation et la qualité du circuit (profondeur et nombre de portes)\n",
        "* Comment personnaliser l'heuristique de routage SABRE (`basic`, `decay`, `lookahead`) et comparer ses performances sur du matériel\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prérequis\n",
        "\n",
        "Nous vous recommandons de vous familiariser avec les sujets suivants avant de commencer ce tutoriel :\n",
        "\n",
        "* [Transpilation de circuits](/docs/guides/transpile) : aperçu de la transpilation dans Qiskit\n",
        "* [Étapes du transpileur](/docs/guides/transpiler-stages) : étapes de disposition et de routage\n",
        "* [Configurer les gestionnaires de passes prédéfinis](/docs/guides/transpile-with-pass-managers) : personnalisation des niveaux d'optimisation\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## Arrière-plan\n",
        "\n",
        "La transpilation permet de convertir des circuits quantiques en formats compatibles avec un matériel quantique spécifique. Deux étapes clés consistent à choisir une **configuration des qubits** (mise en correspondance des qubits logiques avec les qubits physiques) et à définir **le routage des portes** (insertion de portes SWAP afin que les portes multi-qubits respectent la connectivité du dispositif).\n",
        "\n",
        "**SABRE** ( *algorithme de recherche heuristique bidirectionnelle basé sur la méthode SWAP* ) optimise à la fois la disposition et le routage. Cette méthode s'avère particulièrement efficace pour les circuits à grande échelle (plus de 100 qubits) sur des dispositifs présentant des cartes de couplage complexes, comme les processeurs Heron d' IBM®. SABRE minimise le nombre de portes SWAP et réduit la profondeur du circuit, ce qui améliore la fidélité d'exécution. Les récentes améliorations apportées à l'algorithme « [LightSABRE](https://arxiv.org/abs/2409.08368) » permettent de réduire encore davantage les temps d'exécution et le nombre de portes logiques.\n",
        "\n",
        "Dans ce tutoriel, vous allez tout d'abord configurer `SabreLayout` le circuit GHZ à l'aide de différents paramètres afin de l'optimiser, puis observer l'impact de ces réglages sur la fidélité d'exécution. Vous comparerez ensuite les algorithmes de calcul d'itinéraires de SABRE à grande échelle sur du matériel réel.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50cf9d9f-c875-49d5-83b0-c691363615ab",
      "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 version ultérieure, avec prise en charge [de la visualisation](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.22 ou version ultérieure (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6d9ad2c3-1b10-4a22-8b75-608869be0ac7",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuration\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6d93c123-b92e-434d-8361-8115509e6d5f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Using backend: ibm_kingston\n"
          ]
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import EstimatorOptions\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
        "from qiskit_aer.primitives import EstimatorV2 as AerEstimator\n",
        "from qiskit.transpiler.passes import (\n",
        "    SabreLayout,\n",
        "    SabreSwap,\n",
        "    BarrierBeforeFinalMeasurements,\n",
        "    StarPreRouting,\n",
        ")\n",
        "from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit.passmanager.flow_controllers import ConditionalController\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import time\n",
        "\n",
        "seed = 42\n",
        "\n",
        "service = QiskitRuntimeService(\n",
        "    channel=\"ibm_cloud\",\n",
        "    token=\"<YOUR_API_TOKEN>\",  # Replace with your actual API token\n",
        "    instance=\"<YOUR_INSTANCE_NAME>\",  # Replace with your instance name if needed\n",
        ")\n",
        "backend = service.least_busy(operational=True, simulator=False)\n",
        "\n",
        "\n",
        "print(f\"Using backend: {backend.name}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c34842c6-5e1b-4658-8e91-15149280c783",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemple de simulateur à petite échelle\n",
        "\n",
        "Dans cette section, un **simulateur avec bruit,** basé sur le modèle de bruit du backend réel, est utilisé pour montrer comment différentes `SabreLayout` configurations influencent à la fois la qualité de la transpilation et la fidélité d'exécution. L'utilisation de `qiskit_aer` avec un modèle de bruit dérivé de données réelles d'étalonnage matériel vous permet de tester la transpilation sans consommer de crédits matériels.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a2bcc2b2-727b-447f-b680-a23e3b9e0c3c",
      "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",
        "Nous construisons un **circuit GHZ à topologie en étoile** comportant 15 qubits. Le premier qubit sert de nœud central, et des portes CNOT le relient directement à tous les autres qubits. Cette topologie pose un problème complexe d'agencement, car elle ne se traduit pas de manière évidente par la carte de couplage du dispositif.\n",
        "\n",
        "Nous définissons `ZZ` également des opérateurs permettant de mesurer les corrélations d'intrication $\\langle Z_0 Z_i \\rangle$ entre des paires de qubits.\n",
        "\n",
        "![ghz\\_star\\_topology.png](https://quantum.cloud.ibm.com/docs/images/tutorials/transpilation-optimizations-with-sabre/ghz_star_topology.avif)\n",
        "\n",
        "<Admonition type=\"note\" title=\"Lorsque l'on connaît la structure du circuit\">\n",
        "  SABRE est un algorithme polyvalent qui ne repose sur aucune hypothèse concernant la structure du circuit. Pour ce circuit GHZ à topologie en étoile, on connaît en effet un acheminement optimal : le [`StarPreRouting`](/docs/api/qiskit/qiskit.transpiler.passes.StarPreRouting) « pass » détecte les sous-circuits en étoile et les réorganise en une chaîne linéaire qui s'adapte directement à n'importe quel backend disposant d'un chemin linéaire suffisamment long. Ce tutoriel se concentre sur SABRE car cet outil fonctionne pour des circuits quelconques, mais si vous savez que `StarPreRouting` votre circuit présente une structure particulière bien définie, l'application d'une étape spécialisée avant le routage peut donner de meilleurs résultats que n'importe quelle recherche heuristique.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "08a8c4df-b1a5-45b7-9808-ca4e5d7631d8",
      "metadata": {},
      "outputs": [],
      "source": [
        "num_qubits_sim = 15\n",
        "\n",
        "# Create star-topology GHZ circuit\n",
        "qc_sim = QuantumCircuit(num_qubits_sim)\n",
        "qc_sim.h(0)\n",
        "for i in range(1, num_qubits_sim):\n",
        "    qc_sim.cx(0, i)\n",
        "qc_sim.measure_all()\n",
        "\n",
        "# ZZ operators: Z on qubit 0 and qubit i, identity elsewhere\n",
        "operator_strings_sim = [\n",
        "    \"Z\" + \"I\" * i + \"Z\" + \"I\" * (num_qubits_sim - 2 - i)\n",
        "    for i in range(num_qubits_sim - 1)\n",
        "]\n",
        "operators_sim = [SparsePauliOp(op) for op in operator_strings_sim]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "25ac8d92-6362-4d4e-a133-2914703df658",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique\n",
        "\n",
        "`SabreLayout`Le gestionnaire de passes prédéfini par défaut `optimization_level=3` utilise déjà cette fonctionnalité, mais avec des paramètres par défaut prudents. Afin d'étudier l'impact d'un paramétrage plus strict, ce passage est remplacé par une configuration personnalisée `SabreLayout` permettant une recherche plus approfondie, tandis que tous les autres passages de la phase de mise en page restent inchangés. À titre de comparaison, un quatrième gestionnaire de passes conserve la valeur par défaut `SabreLayout` mais ajoute [`StarPreRouting`](/docs/api/qiskit/qiskit.transpiler.passes.StarPreRouting) à la phase d'initialisation. `StarPreRouting` Il s'agit d'une étape *tenant compte de la structure* qui détecte les sous-circuits en étoile et les réécrit sous forme de chaîne linéaire avant le routage.\n",
        "\n",
        "Le déroulement des opérations est le suivant :\n",
        "\n",
        "1. **Examinez** le gestionnaire de passes par défaut pour voir où `SabreLayout` il se situe au sein de la `layout` phase.\n",
        "2. **Remplacez** ce passage par une instance personnalisée `SabreLayout` à l'aide de `PassManager.replace(index, passes=...)`, puis générez la `pm_star` variante avec `pm.init += StarPreRouting()`.\n",
        "3. **Lancez** les quatre gestionnaires de passes et comparez les indicateurs.\n",
        "\n",
        "Les quatre configurations sont les suivantes :\n",
        "\n",
        "| Config              | Description                                                                                                     |\n",
        "| ------------------- | --------------------------------------------------------------------------------------------------------------- |\n",
        "| `pm_1` (par défaut) | Préréglage par défaut « level-3 » (`SabreLayout` avec `max_iterations=4`, `layout_trials=20`, `swap_trials=20`) |\n",
        "| `pm_2`              | Personnalisé `SabreLayout` (`max_iterations=4`, `layout_trials=200`, `swap_trials=200`)                         |\n",
        "| `pm_3`              | Personnalisé `SabreLayout` (`max_iterations=8`, `layout_trials=200`, `swap_trials=200`)                         |\n",
        "| `pm_star`           | Préréglage par défaut avec `StarPreRouting` ajouté à la phase d'initialisation                                  |\n",
        "\n",
        "**Paramètres clés de SABRE :**\n",
        "\n",
        "* **`layout_trials` / `swap_trials`**: Permet de contrôler le nombre de mises en page et de solutions de routage que SABRE examine. En augmentant le nombre d'essais, SABRE explore un espace de recherche plus vaste, ce qui augmente les chances de trouver une meilleure solution.\n",
        "* **`max_iterations`**: Détermine le nombre de cycles d'affinement de routage avant-arrière que SABRE effectue sur chaque candidat. SABRE améliore la disposition de manière itérative en tirant les leçons des retours d'expérience sur le routage; ainsi, plus il y a d'itérations, plus les améliorations sont importantes.\n",
        "\n",
        "Ces deux approches entraînent un temps de transpilation plus long, mais les circuits obtenus sont plus courts et utilisent moins de portes, ce qui réduit directement la décohérence et les erreurs de porte sur le matériel réel.\n",
        "\n",
        "**Étape n° 2a: z le gestionnaire de mots de passe par défaut.** A `StagedPassManager` est constitué d'étapes (`init`, `layout`, `routing`, `translation`, `optimization`, `scheduling`), chacune étant elle-même un `PassManager`. L'appel `.draw()` d'une fonction sur une scène représente ses passes sous forme de graphe, ce qui nous permet de voir où `SabreLayout` se trouve.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "b40fe1e0-41cd-4e8b-acb9-801872d35f1f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/b40fe1e0-41cd-4e8b-acb9-801872d35f1f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Build the default pass manager (no modifications yet)\n",
        "pm_1 = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend, seed_transpiler=seed\n",
        ")\n",
        "\n",
        "# Visualize the layout stage to see where SabreLayout sits\n",
        "pm_1.layout.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f9e5393-af3b-4d3d-a0d8-d20585b02895",
      "metadata": {},
      "source": [
        "Dans le schéma ci-dessus, le `SabreLayout` « pass » que nous souhaitons personnaliser se trouve à la `ConditionalController` position **\\[2]** de l'étape de mise en page. Ce contrôleur remplit deux fonctions :\n",
        "\n",
        "* Il fonctionne `SabreLayout` selon un mécanisme de déclenchement, de sorte qu’il ne s’exécute que lorsque `VF2Layout` la recherche d’une correspondance parfaite \\[a] échoué (sinon, la disposition parfaite de « VF2 » est conservée).\n",
        "* Elle est `SabreLayout` précédée d’une `BarrierBeforeFinalMeasurements` étape qui empêche les mesures d’être réorganisées lors du routage interne d’ SabreLayout's.\n",
        "\n",
        "Si nous nous contentons de `replace(index=2, passes=sl_2)`, ces deux comportements sont abandonnés. Pour les conserver, nous réemballons nos produits sur mesure `SabreLayout` dans le même `ConditionalController` emballage (dans les mêmes conditions et avec la même protection) avant de les remplacer.\n",
        "\n",
        "**Étape n° 2b: : Créez des passes personnalisées `SabreLayout` et remplacez celles par défaut.**\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "79075a21-8f36-4fd9-9d0d-bd0e97395b60",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/79075a21-8f36-4fd9-9d0d-bd0e97395b60-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "cmap = backend.coupling_map\n",
        "\n",
        "# Custom SabreLayout passes with more aggressive search\n",
        "sl_2 = SabreLayout(\n",
        "    coupling_map=cmap,\n",
        "    seed=seed,\n",
        "    max_iterations=4,\n",
        "    layout_trials=200,\n",
        "    swap_trials=200,\n",
        ")\n",
        "sl_3 = SabreLayout(\n",
        "    coupling_map=cmap,\n",
        "    seed=seed,\n",
        "    max_iterations=8,\n",
        "    layout_trials=200,\n",
        "    swap_trials=200,\n",
        ")\n",
        "\n",
        "\n",
        "# Same condition the preset uses: only run SabreLayout when VF2Layout did not\n",
        "# find a perfect mapping. This preserves any perfect layout VF2 produced at [1].\n",
        "def _vf2_match_not_found(property_set):\n",
        "    if property_set[\"layout\"] is None:\n",
        "        return True\n",
        "    return (\n",
        "        property_set[\"VF2Layout_stop_reason\"] is not None\n",
        "        and property_set[\"VF2Layout_stop_reason\"]\n",
        "        is not VF2LayoutStopReason.SOLUTION_FOUND\n",
        "    )\n",
        "\n",
        "\n",
        "def wrap_sabre(sabre_pass):\n",
        "    \"\"\"Re-wrap a SabreLayout in the original ConditionalController + barrier.\"\"\"\n",
        "    return ConditionalController(\n",
        "        [\n",
        "            BarrierBeforeFinalMeasurements(\n",
        "                \"qiskit.transpiler.internal.routing.protection.barrier\"\n",
        "            ),\n",
        "            sabre_pass,\n",
        "        ],\n",
        "        condition=_vf2_match_not_found,\n",
        "    )\n",
        "\n",
        "\n",
        "# Build two fresh pass managers and swap in the wrapped custom SabreLayout at index 2\n",
        "pm_2 = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend, seed_transpiler=seed\n",
        ")\n",
        "pm_3 = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend, seed_transpiler=seed\n",
        ")\n",
        "pm_2.layout.replace(index=2, passes=wrap_sabre(sl_2))\n",
        "pm_3.layout.replace(index=2, passes=wrap_sabre(sl_3))\n",
        "\n",
        "# Build pm_star: default preset with StarPreRouting added to the init stage\n",
        "pm_star = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend, seed_transpiler=seed\n",
        ")\n",
        "pm_star.init += StarPreRouting()\n",
        "\n",
        "# Visualize pm_3 after replacement (pm_2 has the same structure, only max_iterations differs)\n",
        "pm_3.layout.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "378a5829-b18b-4359-827f-b052c7cc1b89",
      "metadata": {},
      "source": [
        "La position **\\[2]** est à nouveau un `ConditionalController` — de forme identique à celle par défaut, mais le intérieur `SabreLayout` est celui que nous avons défini (avec `layout_trials=200`, `swap_trials=200`, et `max_iterations=8` pour `pm_3`; `pm_2` est identique à l'exception de `max_iterations=4`). La barrière de protection et le `_vf2_match_not_found` système de commande sont conservés; la seule différence entre `pm_2`/`pm_3` et `pm_1` réside donc dans la configuration SABRE elle-même. `pm_star` conserve la valeur par défaut `SabreLayout` et ajoute `StarPreRouting` simplement à la fin de la phase d'initialisation.\n",
        "\n",
        "**Étape n° 2c: : Lancez chaque gestionnaire de passes et comparez les résultats.**\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "fd52f8dd-862b-46e2-b93d-0b35f47a3d83",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "pm_1 (4,20,20): 2Q Depth 38, Size 183, Time 0.01s\n",
            "pm_2 (4,200,200): 2Q Depth 36, Size 183, Time 0.15s\n",
            "pm_3 (8,200,200): 2Q Depth 30, Size 158, Time 0.16s\n",
            "pm_star (default + StarPreRouting): 2Q Depth 26, Size 160, Time 0.01s\n",
            "\n",
            "Improvement vs. default (pm_1):\n",
            "  pm_2 (4,200,200): 2Q depth +5.3%, size +0.0%\n",
            "  pm_3 (8,200,200): 2Q depth +21.1%, size +13.7%\n",
            "  pm_star (default + StarPreRouting): 2Q depth +31.6%, size +12.6%\n"
          ]
        }
      ],
      "source": [
        "results_sim = {}\n",
        "for name, pm in [\n",
        "    (\"pm_1 (4,20,20)\", pm_1),\n",
        "    (\"pm_2 (4,200,200)\", pm_2),\n",
        "    (\"pm_3 (8,200,200)\", pm_3),\n",
        "    (\"pm_star (default + StarPreRouting)\", pm_star),\n",
        "]:\n",
        "    t0 = time.time()\n",
        "    tqc = pm.run(qc_sim)\n",
        "    elapsed = time.time() - t0\n",
        "    depth = tqc.depth(lambda x: x.operation.num_qubits == 2)\n",
        "    size = tqc.size()\n",
        "    ops_mapped = [op.apply_layout(tqc.layout) for op in operators_sim]\n",
        "    results_sim[name] = {\n",
        "        \"tqc\": tqc,\n",
        "        \"ops\": ops_mapped,\n",
        "        \"depth\": depth,\n",
        "        \"size\": size,\n",
        "        \"time\": elapsed,\n",
        "    }\n",
        "    print(f\"{name}: 2Q Depth {depth}, Size {size}, Time {elapsed:.2f}s\")\n",
        "\n",
        "# Print improvement relative to default (pm_1)\n",
        "baseline = results_sim[\"pm_1 (4,20,20)\"]\n",
        "print(\"\\nImprovement vs. default (pm_1):\")\n",
        "for name in [\n",
        "    \"pm_2 (4,200,200)\",\n",
        "    \"pm_3 (8,200,200)\",\n",
        "    \"pm_star (default + StarPreRouting)\",\n",
        "]:\n",
        "    r = results_sim[name]\n",
        "    depth_pct = (baseline[\"depth\"] - r[\"depth\"]) / baseline[\"depth\"] * 100\n",
        "    size_pct = (baseline[\"size\"] - r[\"size\"]) / baseline[\"size\"] * 100\n",
        "    print(f\"  {name}: 2Q depth {depth_pct:+.1f}%, size {size_pct:+.1f}%\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c27cbdb-c065-4a36-a0ef-6c9572c7a4a5",
      "metadata": {},
      "source": [
        "Les trois gestionnaires de passes modifiés ont tous généré des circuits présentant une profondeur d' 2Q s inférieure à celle du gestionnaire par défaut. Les configurations SABRE agressives (`pm_2` et `pm_3`) sacrifient un temps de transpilation plus long au profit d'une recherche plus large, tandis que `pm_star` tire parti de la structure en étoile du circuit et produit un résultat encore moins profond sans entraîner de coût de transpilation supplémentaire. Les gains exacts varient d'une exécution à l'autre, mais la tendance générale reste la même : un plus grand nombre d'essais et d'itérations SABRE permet à l'algorithme heuristique d'explorer un espace plus vaste, tandis que les passes tenant compte de la structure, comme `StarPreRouting` , peuvent contourner entièrement cette recherche lorsque la forme du circuit correspond.\n",
        "\n",
        "Même à cette petite échelle (15 qubits), la marge d'amélioration est suffisante pour que les trois approches surpassent l'approche par défaut. Avec des circuits plus grands (plus de 100 qubits), l'espace de recherche s'étend considérablement et les avantages liés à la fois à l'augmentation du nombre d'essais et aux passages tenant compte de la structure deviennent beaucoup plus marqués, comme le montrera la section consacrée aux circuits à grande échelle.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "bf75c45a-2c3e-4ef6-8336-0b3f69e6e8fb",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/bf75c45a-2c3e-4ef6-8336-0b3f69e6e8fb-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "pm_names = list(results_sim.keys())\n",
        "depths = [results_sim[n][\"depth\"] for n in pm_names]\n",
        "sizes = [results_sim[n][\"size\"] for n in pm_names]\n",
        "times = [results_sim[n][\"time\"] for n in pm_names]\n",
        "colors = [\"#404080\", \"#2a9d8f\", \"#a8d05e\", \"#e29bdd\"]\n",
        "x = np.arange(len(pm_names))\n",
        "\n",
        "fig, axs = plt.subplots(1, 3, figsize=(14, 5))\n",
        "\n",
        "# 2Q Depth\n",
        "bars = axs[0].bar(x, depths, color=colors)\n",
        "axs[0].set_ylabel(\"2Q Depth\", fontsize=11)\n",
        "axs[0].set_title(\"Two-Qubit Gate Depth\", fontsize=13)\n",
        "axs[0].set_ylim(0, max(depths) * 1.2)\n",
        "for bar, val in zip(bars, depths):\n",
        "    axs[0].text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + max(depths) * 0.02,\n",
        "        str(val),\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=11,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "for i in range(1, len(depths)):\n",
        "    pct = (depths[0] - depths[i]) / depths[0] * 100\n",
        "    if pct != 0:\n",
        "        axs[0].text(\n",
        "            bars[i].get_x() + bars[i].get_width() / 2,\n",
        "            bars[i].get_height() / 2,\n",
        "            f\"{pct:+.0f}%\",\n",
        "            ha=\"center\",\n",
        "            va=\"center\",\n",
        "            fontsize=10,\n",
        "            color=\"white\",\n",
        "            fontweight=\"bold\",\n",
        "        )\n",
        "\n",
        "# Size\n",
        "bars = axs[1].bar(x, sizes, color=colors)\n",
        "axs[1].set_ylabel(\"Gate Count\", fontsize=11)\n",
        "axs[1].set_title(\"Circuit Size\", fontsize=13)\n",
        "axs[1].set_ylim(0, max(sizes) * 1.2)\n",
        "for bar, val in zip(bars, sizes):\n",
        "    axs[1].text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + max(sizes) * 0.02,\n",
        "        str(val),\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=11,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "for i in range(1, len(sizes)):\n",
        "    pct = (sizes[0] - sizes[i]) / sizes[0] * 100\n",
        "    if abs(pct) > 0.1:\n",
        "        axs[1].text(\n",
        "            bars[i].get_x() + bars[i].get_width() / 2,\n",
        "            bars[i].get_height() / 2,\n",
        "            f\"{pct:+.0f}%\",\n",
        "            ha=\"center\",\n",
        "            va=\"center\",\n",
        "            fontsize=10,\n",
        "            color=\"white\",\n",
        "            fontweight=\"bold\",\n",
        "        )\n",
        "\n",
        "# Time\n",
        "bars = axs[2].bar(x, times, color=colors)\n",
        "axs[2].set_ylabel(\"Time (s)\", fontsize=11)\n",
        "axs[2].set_title(\"Transpilation Time\", fontsize=13)\n",
        "axs[2].set_ylim(0, max(times) * 1.3)\n",
        "for bar, val in zip(bars, times):\n",
        "    axs[2].text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + max(times) * 0.03,\n",
        "        f\"{val:.2f}s\",\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=11,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "\n",
        "for ax in axs:\n",
        "    ax.set_xticks(x)\n",
        "    ax.set_xticklabels(pm_names, fontsize=8, rotation=15)\n",
        "    ax.grid(axis=\"y\", linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "plt.suptitle(\n",
        "    \"Transpilation quality vs. configuration\",\n",
        "    fontsize=14,\n",
        "    fontweight=\"bold\",\n",
        "    y=1.02,\n",
        ")\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6a233965-1358-420b-8823-5f4e206090b4",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Étape 3 : Exécutez à l'aide d' Qiskit primitives\n",
        "\n",
        "Nous exécutons chaque circuit transpilé **10 fois** à l'aide d'Aer `EstimatorV2` , avec un modèle de bruit dérivé du backend réel. Étant donné que les résultats de simulation, sujets à des variations aléatoires, diffèrent d'une exécution à l'autre, le calcul de la moyenne sur plusieurs exécutions permet d'obtenir des estimations de fidélité plus fiables et de quantifier l'incertitude statistique à l'aide de barres d'erreur.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "a91b9887-c8cf-48fd-a6fb-a5506d201f8d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Run 1/10 done\n",
            "Run 2/10 done\n",
            "Run 3/10 done\n",
            "Run 4/10 done\n",
            "Run 5/10 done\n",
            "Run 6/10 done\n",
            "Run 7/10 done\n",
            "Run 8/10 done\n",
            "Run 9/10 done\n",
            "Run 10/10 done\n",
            "pm_1 (4,20,20): mean fidelity = 0.9510 +/- 0.0094\n",
            "pm_2 (4,200,200): mean fidelity = 0.9513 +/- 0.0043\n",
            "pm_3 (8,200,200): mean fidelity = 0.9540 +/- 0.0065\n",
            "pm_star (default + StarPreRouting): mean fidelity = 0.9547 +/- 0.0072\n"
          ]
        }
      ],
      "source": [
        "# Create a noisy estimator from the real backend's noise model\n",
        "noisy_estimator = AerEstimator.from_backend(backend)\n",
        "\n",
        "num_runs = 10\n",
        "# sim_all_runs[name] = list of arrays, one per run\n",
        "sim_all_runs = {name: [] for name in results_sim}\n",
        "\n",
        "for run in range(num_runs):\n",
        "    for name, r in results_sim.items():\n",
        "        job = noisy_estimator.run([(r[\"tqc\"], r[\"ops\"])])\n",
        "        evs = list(job.result()[0].data.evs)\n",
        "        sim_all_runs[name].append(evs)\n",
        "    print(f\"Run {run + 1}/{num_runs} done\")\n",
        "\n",
        "# Compute mean and std across runs for each config\n",
        "sim_stats = {}\n",
        "for name in results_sim:\n",
        "    all_evs = np.array(sim_all_runs[name])  # shape (num_runs, num_operators)\n",
        "    sim_stats[name] = {\n",
        "        \"mean\": np.mean(all_evs, axis=0),\n",
        "        \"std\": np.std(all_evs, axis=0),\n",
        "        \"overall_mean\": np.mean(all_evs),\n",
        "        \"overall_std\": np.std(\n",
        "            np.mean(all_evs, axis=1)\n",
        "        ),  # std of per-run averages\n",
        "    }\n",
        "    print(\n",
        "        f\"{name}: mean fidelity = {sim_stats[name]['overall_mean']:.4f} +/- {sim_stats[name]['overall_std']:.4f}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89e6c70f-4da4-470f-b0fa-2e2521c82f6f",
      "metadata": {},
      "source": [
        "Comme il s'agit d'un petit circuit, les valeurs de fidélité sont relativement proches dans les quatre configurations. Les circuits sont suffisamment courts pour que le bruit matériel n'ait pas d'impact significatif, même sur la version la moins optimisée. La fidélité moyenne suit globalement la profondeur d’ 2Q : `pm_3` et `pm_star`, les deux circuits les moins profonds, atteignent les fidélités les plus élevées et se situent pratiquement à égalité, compte tenu de leurs marges d’erreur. `pm_2` constitue un contre-exemple intéressant : bien que sa profondeur d' 2Q e soit inférieure à `pm_1`celle de, sa fidélité moyenne s'avère également légèrement inférieure, ce qui nous rappelle que le lien entre profondeur et fidélité est de nature statistique plutôt que déterministe. Le choix des qubits spécifiques dans une configuration et leur étalonnage au moment de l'exécution ont également leur importance.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e601648b-8f5f-4111-ba35-f64af63da909",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité\n",
        "\n",
        "Tracez ensuite les corrélations d'intrication $\\langle Z_0 Z_i \\rangle$ en fonction de la distance entre les qubits, ainsi que la **corrélation moyenne** en tant qu'indicateur unique de fidélité. Dans un cas idéal (sans bruit), toutes les corrélations seraient égales à 1. Compte tenu des bruits réels, chaque porte supplémentaire introduit une erreur et chaque pas de temps supplémentaire favorise la décohérence; ainsi, un circuit transpilé présentant une profondeur moindre et un nombre réduit de portes (en particulier de portes à deux qubits) devrait mieux préserver l'intrication.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "a6dac5ed-a963-458a-ada1-89c915f036e0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/a6dac5ed-a963-458a-ada1-89c915f036e0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "data_sim = list(range(1, len(operators_sim) + 1))\n",
        "markers = [\"o\", \"s\", \"^\", \"*\"]\n",
        "colors_line = [\"#404080\", \"#2a9d8f\", \"#a8d05e\", \"#e29bdd\"]\n",
        "\n",
        "fig, (ax1, ax2) = plt.subplots(\n",
        "    1, 2, figsize=(14, 5), gridspec_kw={\"width_ratios\": [2.5, 1]}\n",
        ")\n",
        "\n",
        "# Left: correlations vs distance with error bars (mean +/- 1 std)\n",
        "for (name, stats), marker, color in zip(\n",
        "    sim_stats.items(), markers, colors_line\n",
        "):\n",
        "    ax1.errorbar(\n",
        "        data_sim,\n",
        "        stats[\"mean\"],\n",
        "        yerr=stats[\"std\"],\n",
        "        marker=marker,\n",
        "        label=name,\n",
        "        color=color,\n",
        "        linewidth=2,\n",
        "        capsize=3,\n",
        "        capthick=1,\n",
        "        elinewidth=1,\n",
        "    )\n",
        "\n",
        "ax1.set_xlabel(\"Distance between qubits $i$\", fontsize=11)\n",
        "ax1.set_ylabel(r\"$\\langle Z_0 Z_i \\rangle$\", fontsize=11)\n",
        "ax1.set_title(\n",
        "    \"Entanglement correlations vs. qubit distance (avg. of 10 runs)\",\n",
        "    fontsize=12,\n",
        ")\n",
        "ax1.legend(fontsize=9)\n",
        "ax1.grid(alpha=0.3)\n",
        "\n",
        "# Right: mean correlation bar chart with error bars\n",
        "names = list(sim_stats.keys())\n",
        "means = [sim_stats[n][\"overall_mean\"] for n in names]\n",
        "stds = [sim_stats[n][\"overall_std\"] for n in names]\n",
        "x_bar = np.arange(len(names))\n",
        "bars = ax2.bar(\n",
        "    x_bar, means, yerr=stds, color=colors_line, capsize=5, ecolor=\"gray\"\n",
        ")\n",
        "ax2.set_ylabel(r\"Mean $\\langle Z_0 Z_i \\rangle$\", fontsize=11)\n",
        "ax2.set_title(\"Average fidelity\", fontsize=13, pad=12)\n",
        "y_range = max(means) - min(means) if max(means) != min(means) else 0.01\n",
        "# Top of ylim accounts for the bar height + std error bar + headroom for the value label\n",
        "y_top = max(m + s for m, s in zip(means, stds)) + y_range * 1.5\n",
        "ax2.set_ylim(min(means) - y_range * 0.8, y_top)\n",
        "for bar, val, std in zip(bars, means, stds):\n",
        "    ax2.text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + std + y_range * 0.15,\n",
        "        f\"{val:.4f}\",\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=10,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "# Annotate % change vs pm_1\n",
        "baseline_mean = means[0]\n",
        "for i in range(1, len(means)):\n",
        "    pct = (means[i] - baseline_mean) / baseline_mean * 100\n",
        "    if abs(pct) > 0.01:\n",
        "        mid_y = (means[i] + ax2.get_ylim()[0]) / 2\n",
        "        ax2.text(\n",
        "            bars[i].get_x() + bars[i].get_width() / 2,\n",
        "            mid_y,\n",
        "            f\"{pct:+.1f}%\",\n",
        "            ha=\"center\",\n",
        "            va=\"center\",\n",
        "            fontsize=10,\n",
        "            color=\"white\",\n",
        "            fontweight=\"bold\",\n",
        "        )\n",
        "ax2.set_xticks(x_bar)\n",
        "ax2.set_xticklabels(names, fontsize=8, rotation=15)\n",
        "ax2.grid(axis=\"y\", linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "fig.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "05235ab1-b473-4267-a34c-5f56e728550b",
      "metadata": {},
      "source": [
        "Les résultats montrent un lien évident entre la qualité de la transpilation et la fidélité de l'exécution, avec quelques précisions utiles :\n",
        "\n",
        "* **`pm_1` (par défaut)** : Référence. Avec seulement 20 essais et quatre itérations, SABRE dispose d'une marge d'optimisation limitée, ce qui se traduit par le circuit le plus profond parmi ceux générés uniquement par SABRE.\n",
        "* **`pm_2` (autres essais)** : L'exploration d'un nombre de candidats dix fois plus important permet d'obtenir une structure légèrement moins profonde, mais la fidélité moyenne reste globalement stable (et peut même descendre en dessous de la valeur de référence en raison du bruit), car le gain en profondeur est faible à cette échelle.\n",
        "* **`pm_3` (plus d'essais + plus d'itérations)** : En doublant `max_iterations` ce nombre pour le porter à 8, SABRE bénéficie de cycles de raffinement supplémentaires, ce qui permet d'obtenir le circuit le moins profond réalisé uniquement avec SABRE et la fidélité moyenne la plus élevée de la comparaison.\n",
        "* **`pm_star` (par défaut + StarPreRouting )** : Ajoute `StarPreRouting` à la phase d'initialisation d'un préréglage qui, sans cela, serait celui par défaut. La réécriture tenant compte de la structure réduit la structure en étoile à une chaîne linéaire que le reste du transpileur mappe sur le chemin linéaire du dispositif, produisant ainsi le circuit globalement le moins profond (légèrement meilleur que `pm_3`) et offrant `pm_3` une fidélité équivalente à celle de dans les limites des marges d'erreur. Cela ne modifie pas le temps de transpilation par rapport à la valeur par défaut, car la réécriture ne nécessite pratiquement aucun temps d'exécution par rapport à la recherche stochastique de SABRE.\n",
        "\n",
        "Il convient de noter que l'augmentation de `max_iterations` n'a pas toujours un impact positif. Dans ce cas précis, cela a été très utile, mais pour d’autres circuits ou backends, les itérations supplémentaires pourraient ne pas apporter d’amélioration supplémentaire, voire nuire légèrement aux performances en raison d’une optimisation excessive d’un minimum local. En règle générale, vous devriez augmenter `layout_trials` et `swap_trials` autant que votre budget-temps le permet, car multiplier les essais augmente toujours les chances de trouver une meilleure disposition. Il vaut la peine de tester cette augmentation `max_iterations` , mais elle doit être validée en fonction de votre cas d'utilisation spécifique. Les passes spécialisées telles que `StarPreRouting` sont similaires dans leur principe, mais dépendent davantage du circuit : elles ne sont utiles que lorsque le circuit contient effectivement la structure qu'elles ciblent. Le gain est important lorsque cela s'applique, et nul dans le cas contraire, mais cela ne coûte pratiquement rien d'essayer.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c2b60e42-4aa2-4dc1-8a11-022072e79776",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Exemple de matériel à grande échelle\n",
        "\n",
        "Outre l'ajustement du nombre d'essais, SABRE permet de personnaliser **l** 'algorithme de routage. SABRE propose trois méthodes heuristiques :\n",
        "\n",
        "* **`basic`**: Une approche gloutonne simple qui sélectionne l'échange minimisant la distance immédiate jusqu'à la porte suivante.\n",
        "* **`decay`** (par défaut) : attribue dynamiquement des poids aux qubits en fonction de leur activité récente, ce qui décourage les échanges répétés sur les mêmes qubits.\n",
        "* **`lookahead`**: Évalue les coûts de routage futurs en anticipant les prochaines portes, ce qui permet éventuellement de trouver de meilleures séquences d'échange.\n",
        "\n",
        "Pour utiliser une heuristique personnalisée, créez un `SabreSwap` « pass » et associez-le à `SabreLayout` via le `routing_pass` paramètre.\n",
        "\n",
        "`SabreLayout`Un quatrième gestionnaire de passes est ajouté à la comparaison : `pm_star_hw`, qui conserve les paramètres par`SabreSwap` défaut mais ajoute `StarPreRouting` à la phase d'initialisation. À cette échelle (100 qubits), la recherche SABRE s'avère plus complexe, et la conversion d'une structure en étoile en une chaîne linéaire s'avère clairement avantageuse, car un processeur Heron dispose de chemins linéaires suffisamment longs pour accueillir le circuit obtenu.\n",
        "\n",
        "Nous comparons ici les trois heuristiques SABRE, ainsi `StarPreRouting` que leur application à grande échelle sur un circuit GHZ de 100 qubits. Nous effectuons plusieurs essais de disposition avec différentes graines pour les configurations SABRE, sélectionnons le meilleur circuit transpilé de chacun d'entre eux, puis les soumettons tous à un test sur du matériel réel, ainsi que `StarPreRouting` le résultat.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d755fafa-bbbc-4191-9ad9-a77f2af1bedc",
      "metadata": {},
      "source": [
        "<span id=\"steps-1-4-compressed-into-a-single-code-block\" />\n",
        "\n",
        "### Étapes 1 à 4 regroupées en un seul bloc de code\n",
        "\n",
        "C'est ici que l'ensemble du processus est mis en œuvre à plus grande échelle. Lorsque l'on utilise `SabreSwap` comme pour `routing_pass` `SabreLayout`, un seul essai de disposition est effectué par appel; c'est pourquoi la cellule de code suivante effectue une boucle sur les graines afin d'explorer l'espace de disposition.\n",
        "\n",
        "Nous utilisons la même `wrap_sabre` fonction auxiliaire que celle définie dans l’étape 2 à petite échelle (ci-dessus), et ajoutons une fonction auxiliaire analogue `wrap_routing` , car l’étape `routing` à l’indice \\[1] est également un `ConditionalController([BarrierBeforeFinalMeasurements, routing_pass], ...)` —; la remplacer telle quelle supprimerait de la même manière la barrière protectrice et la `_swap_condition` synchronisation.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "4feb0fcf-d305-4741-82a7-b0fe0f0894fc",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "\n",
        "num_qubits = 100\n",
        "\n",
        "# Create star-topology GHZ circuit\n",
        "qc = QuantumCircuit(num_qubits)\n",
        "qc.h(0)\n",
        "for i in range(1, num_qubits):\n",
        "    qc.cx(0, i)\n",
        "qc.measure_all()\n",
        "\n",
        "# ZZ operators\n",
        "operator_strings = [\n",
        "    \"Z\" + \"I\" * i + \"Z\" + \"I\" * (num_qubits - 2 - i)\n",
        "    for i in range(num_qubits - 1)\n",
        "]\n",
        "operators = [SparsePauliOp(op) for op in operator_strings]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "43ec98c6-f4c6-4584-8ba0-edfb4e99f04a",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "basic:\n",
            "  2Q depth: min: 524, mean: 570.5, std: 39.9\n",
            "  size    : min: 3819, mean: 4227.1, std: 360.6\n",
            "  best seed: 51 (2Q depth=524, size=3852)\n",
            "decay:\n",
            "  2Q depth: min: 387, mean: 436.4, std: 41.7\n",
            "  size    : min: 2687, mean: 3183.1, std: 459.3\n",
            "  best seed: 45 (2Q depth=387, size=2786)\n",
            "lookahead:\n",
            "  2Q depth: min: 364, mean: 424.6, std: 36.5\n",
            "  size    : min: 2335, mean: 3014.6, std: 388.1\n",
            "  best seed: 51 (2Q depth=364, size=2485)\n",
            "StarPreRouting:\n",
            "  2Q depth: min: 196, mean: 196.0, std: 0.0\n",
            "  size    : min: 1151, mean: 1151.0, std: 0.0\n",
            "  best seed: 42 (2Q depth=196, size=1151)\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 2-------------------------\n",
        "\n",
        "num_seeds = 10\n",
        "seed_list = [seed + i for i in range(num_seeds)]\n",
        "swap_trials = 200\n",
        "\n",
        "\n",
        "# The default routing[1] is a ConditionalController([barrier, routing_pass],\n",
        "# condition=_swap_condition); we re-wrap so the new routing pass keeps the\n",
        "# protective barrier and is skipped when routing isn't needed (matches the preset).\n",
        "def _swap_condition(property_set):\n",
        "    return not property_set[\"routing_not_needed\"]\n",
        "\n",
        "\n",
        "def wrap_routing(routing_pass):\n",
        "    return ConditionalController(\n",
        "        [\n",
        "            BarrierBeforeFinalMeasurements(\n",
        "                \"qiskit.transpiler.internal.routing.protection.barrier\"\n",
        "            ),\n",
        "            routing_pass,\n",
        "        ],\n",
        "        condition=_swap_condition,\n",
        "    )\n",
        "\n",
        "\n",
        "heuristic_results = {}\n",
        "\n",
        "# Three SABRE heuristics, swept over seeds\n",
        "for heuristic in [\"basic\", \"decay\", \"lookahead\"]:\n",
        "    trials = []\n",
        "    for s in seed_list:\n",
        "        sr = SabreSwap(\n",
        "            coupling_map=cmap, heuristic=heuristic, trials=swap_trials, seed=s\n",
        "        )\n",
        "        sl = SabreLayout(coupling_map=cmap, routing_pass=sr, seed=s)\n",
        "        pm = generate_preset_pass_manager(\n",
        "            optimization_level=3, backend=backend, seed_transpiler=s\n",
        "        )\n",
        "        # Re-wrap each custom pass in its original ConditionalController + barrier\n",
        "        # (wrap_sabre is defined in the small-scale Step 2 cell above).\n",
        "        pm.layout.replace(index=2, passes=wrap_sabre(sl))\n",
        "        pm.routing.replace(index=1, passes=wrap_routing(sr))\n",
        "\n",
        "        t0 = time.time()\n",
        "        tqc = pm.run(qc)\n",
        "        elapsed = time.time() - t0\n",
        "        depth = tqc.depth(lambda x: x.operation.num_qubits == 2)\n",
        "        size = tqc.size()\n",
        "        trials.append(\n",
        "            {\n",
        "                \"tqc\": tqc,\n",
        "                \"depth\": depth,\n",
        "                \"size\": size,\n",
        "                \"time\": elapsed,\n",
        "                \"seed\": s,\n",
        "            }\n",
        "        )\n",
        "\n",
        "    heuristic_results[heuristic] = trials\n",
        "\n",
        "# Default preset + StarPreRouting in init, also swept over seeds for a fair comparison\n",
        "star_trials = []\n",
        "for s in seed_list:\n",
        "    pm_star_hw = generate_preset_pass_manager(\n",
        "        optimization_level=3, backend=backend, seed_transpiler=s\n",
        "    )\n",
        "    pm_star_hw.init += StarPreRouting()\n",
        "\n",
        "    t0 = time.time()\n",
        "    tqc = pm_star_hw.run(qc)\n",
        "    elapsed = time.time() - t0\n",
        "    depth = tqc.depth(lambda x: x.operation.num_qubits == 2)\n",
        "    size = tqc.size()\n",
        "    star_trials.append(\n",
        "        {\n",
        "            \"tqc\": tqc,\n",
        "            \"depth\": depth,\n",
        "            \"size\": size,\n",
        "            \"time\": elapsed,\n",
        "            \"seed\": s,\n",
        "        }\n",
        "    )\n",
        "heuristic_results[\"StarPreRouting\"] = star_trials\n",
        "\n",
        "# Print summary for each entry\n",
        "for label in [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]:\n",
        "    trials = heuristic_results[label]\n",
        "    depths = [t[\"depth\"] for t in trials]\n",
        "    sizes = [t[\"size\"] for t in trials]\n",
        "    best = min(trials, key=lambda t: t[\"depth\"])\n",
        "    print(f\"{label}:\")\n",
        "    print(\n",
        "        f\"  2Q depth: min: {min(depths)}, mean: {np.mean(depths):.1f}, std: {np.std(depths):.1f}\"\n",
        "    )\n",
        "    print(\n",
        "        f\"  size    : min: {min(sizes)}, mean: {np.mean(sizes):.1f}, std: {np.std(sizes):.1f}\"\n",
        "    )\n",
        "    print(\n",
        "        f\"  best seed: {best['seed']} (2Q depth={best['depth']}, size={best['size']})\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "eead9bd2-17e0-4f5b-80bc-eb9b30af052e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/eead9bd2-17e0-4f5b-80bc-eb9b30af052e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "basic: best 2Q depth=524, size=3852 (seed=51)\n",
            "decay: best 2Q depth=387, size=2786 (seed=45)\n",
            "lookahead: best 2Q depth=364, size=2485 (seed=51)\n",
            "StarPreRouting: best 2Q depth=196, size=1151 (seed=42)\n"
          ]
        }
      ],
      "source": [
        "hw_colors = {\n",
        "    \"basic\": \"#ff7f0e\",\n",
        "    \"decay\": \"#d62728\",\n",
        "    \"lookahead\": \"#1f77b4\",\n",
        "    \"StarPreRouting\": \"#2a9d8f\",\n",
        "}\n",
        "\n",
        "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))\n",
        "\n",
        "for label in [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]:\n",
        "    trials = heuristic_results[label]\n",
        "    depths = [t[\"depth\"] for t in trials]\n",
        "    sizes = [t[\"size\"] for t in trials]\n",
        "    seeds = [t[\"seed\"] for t in trials]\n",
        "    color = hw_colors[label]\n",
        "\n",
        "    ax1.scatter(\n",
        "        seeds,\n",
        "        depths,\n",
        "        label=label,\n",
        "        color=color,\n",
        "        alpha=0.8,\n",
        "        edgecolor=\"k\",\n",
        "        s=60,\n",
        "    )\n",
        "    ax1.axhline(np.mean(depths), color=color, linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "    ax2.scatter(\n",
        "        seeds,\n",
        "        sizes,\n",
        "        label=label,\n",
        "        color=color,\n",
        "        alpha=0.8,\n",
        "        edgecolor=\"k\",\n",
        "        s=60,\n",
        "    )\n",
        "    ax2.axhline(np.mean(sizes), color=color, linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "ax1.set_xlabel(\"Seed\", fontsize=11)\n",
        "ax1.set_ylabel(\"2Q Depth\", fontsize=11)\n",
        "ax1.set_title(\"Two-Qubit Gate Depth per Seed\", fontsize=13)\n",
        "ax1.legend(fontsize=10)\n",
        "ax1.grid(alpha=0.3)\n",
        "\n",
        "ax2.set_xlabel(\"Seed\", fontsize=11)\n",
        "ax2.set_ylabel(\"Gate Count\", fontsize=11)\n",
        "ax2.set_title(\"Circuit Size per Seed\", fontsize=13)\n",
        "ax2.legend(fontsize=10)\n",
        "ax2.grid(alpha=0.3)\n",
        "\n",
        "plt.suptitle(\n",
        "    \"Transpilation variability across seeds: SABRE heuristics vs. StarPreRouting\",\n",
        "    fontsize=14,\n",
        "    fontweight=\"bold\",\n",
        "    y=1.02,\n",
        ")\n",
        "plt.tight_layout()\n",
        "plt.show()\n",
        "\n",
        "# Summary comparison\n",
        "for label in [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]:\n",
        "    best = min(heuristic_results[label], key=lambda t: t[\"depth\"])\n",
        "    print(\n",
        "        f\"{label}: best 2Q depth={best['depth']}, size={best['size']} (seed={best['seed']})\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "5ad47245-41d0-4d90-ba94-dda4cd63705d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Best basic: 2Q depth=524, size=3852\n",
            "Best decay: 2Q depth=387, size=2786\n",
            "Best lookahead: 2Q depth=364, size=2485\n",
            "Best StarPreRouting: 2Q depth=196, size=1151\n",
            "basic job: d81q5tnoha1c73bknprg\n",
            "decay job: d81q5tugbeec73aktopg\n",
            "lookahead job: d81q5to0bvlc73d1epe0\n",
            "StarPreRouting job: d81q5u7tjchs73bn82hg\n",
            "basic job done\n",
            "decay job done\n",
            "lookahead job done\n",
            "StarPreRouting job done\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 3: Execute on hardware-------------------------\n",
        "\n",
        "best_circuits = {}\n",
        "for label in [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]:\n",
        "    best_circuits[label] = min(\n",
        "        heuristic_results[label], key=lambda t: t[\"depth\"]\n",
        "    )\n",
        "    b = best_circuits[label]\n",
        "    print(f\"Best {label}: 2Q depth={b['depth']}, size={b['size']}\")\n",
        "\n",
        "options = EstimatorOptions()\n",
        "options.resilience_level = 2\n",
        "options.dynamical_decoupling.enable = True\n",
        "options.dynamical_decoupling.sequence_type = \"XY4\"\n",
        "estimator = Estimator(backend, options=options)\n",
        "\n",
        "hw_jobs = {}\n",
        "hw_ops = {}\n",
        "for label, best in best_circuits.items():\n",
        "    hw_ops[label] = [op.apply_layout(best[\"tqc\"].layout) for op in operators]\n",
        "    hw_jobs[label] = estimator.run([(best[\"tqc\"], hw_ops[label])])\n",
        "    print(f\"{label} job: {hw_jobs[label].job_id()}\")\n",
        "estimator.options.environment.job_tags = [\"TUT_TOWS\"]\n",
        "\n",
        "hw_results = {}\n",
        "for label, job in hw_jobs.items():\n",
        "    hw_results[label] = job.result()[0]\n",
        "    print(f\"{label} job done\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "0280b0b9-6320-43e5-8396-f82f9e718319",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/0280b0b9-6320-43e5-8396-f82f9e718319-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "Mean fidelity:\n",
            "  basic: 0.0344\n",
            "  decay: 0.1298\n",
            "  lookahead: 0.1857\n",
            "  StarPreRouting: 0.3295\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 4: Post-process-------------------------\n",
        "\n",
        "data = list(range(1, len(operators) + 1))\n",
        "hw_markers = {\n",
        "    \"basic\": \"D\",\n",
        "    \"decay\": \"o\",\n",
        "    \"lookahead\": \"s\",\n",
        "    \"StarPreRouting\": \"*\",\n",
        "}\n",
        "hw_labels = [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]\n",
        "\n",
        "fig, (ax1, ax2) = plt.subplots(\n",
        "    1, 2, figsize=(14, 5), gridspec_kw={\"width_ratios\": [2.5, 1]}\n",
        ")\n",
        "\n",
        "# Left: correlations vs distance\n",
        "for label in hw_labels:\n",
        "    evs = list(hw_results[label].data.evs)\n",
        "    b = best_circuits[label]\n",
        "    ax1.plot(\n",
        "        data,\n",
        "        evs,\n",
        "        marker=hw_markers[label],\n",
        "        color=hw_colors[label],\n",
        "        linewidth=2,\n",
        "        label=f\"{label} (2Q depth={b['depth']}, size={b['size']})\",\n",
        "        markersize=5 if label == \"StarPreRouting\" else 4,\n",
        "    )\n",
        "\n",
        "ax1.set_xlabel(\"Distance between qubits $i$\", fontsize=11)\n",
        "ax1.set_ylabel(r\"$\\langle Z_0 Z_i \\rangle$\", fontsize=11)\n",
        "ax1.set_title(\n",
        "    \"Entanglement correlations vs. qubit distance (hardware)\", fontsize=12\n",
        ")\n",
        "ax1.legend(fontsize=9)\n",
        "ax1.grid(alpha=0.3)\n",
        "\n",
        "# Right: mean fidelity bar chart\n",
        "hw_means = [np.mean(list(hw_results[label].data.evs)) for label in hw_labels]\n",
        "hw_bar_colors = [hw_colors[label] for label in hw_labels]\n",
        "x_bar = np.arange(len(hw_labels))\n",
        "bars = ax2.bar(x_bar, hw_means, color=hw_bar_colors)\n",
        "ax2.set_ylabel(r\"Mean $\\langle Z_0 Z_i \\rangle$\", fontsize=11)\n",
        "ax2.set_title(\"Average fidelity\", fontsize=13)\n",
        "y_range = (\n",
        "    max(hw_means) - min(hw_means) if max(hw_means) != min(hw_means) else 0.01\n",
        ")\n",
        "ax2.set_ylim(min(hw_means) - y_range * 0.2, max(hw_means) + y_range * 0.15)\n",
        "for bar, val in zip(bars, hw_means):\n",
        "    ax2.text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + y_range * 0.05,\n",
        "        f\"{val:.4f}\",\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=11,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "ax2.set_xticks(x_bar)\n",
        "ax2.set_xticklabels(hw_labels, fontsize=9, rotation=15)\n",
        "ax2.grid(axis=\"y\", linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "fig.tight_layout()\n",
        "plt.show()\n",
        "\n",
        "print(\"\\nMean fidelity:\")\n",
        "for label, m in zip(hw_labels, hw_means):\n",
        "    print(f\"  {label}: {m:.4f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "151e5fe9-872a-4b89-92f9-85bb883de17b",
      "metadata": {},
      "source": [
        "<span id=\"analysis\" />\n",
        "\n",
        "### Analyse\n",
        "\n",
        "Les nuages de points montrent une variabilité importante entre les différentes graines pour les trois heuristiques SABRE, ce qui souligne l'importance de réaliser plusieurs essais de disposition plutôt que de se fier à une seule transpilation. La `StarPreRouting` courbe reste pratiquement plate pour toutes les graines, car la transformation d'une structure en étoile en une chaîne linéaire est déterministe compte tenu de la structure; le routage SABRE en aval ne dispose alors que d'une très faible marge de manœuvre sur une chaîne linéaire, de sorte que la graine n'a pratiquement aucun effet sur la profondeur ou la taille finales.\n",
        "\n",
        "D'après les résultats de la transpilation, les `decay` heuristiques et `lookahead` surpassent `basic` systématiquement avec une large marge. Cette `basic` heuristique, bien que rapide, utilise une stratégie gloutonne simple qui conduit souvent à des circuits nettement plus profonds. Pour ce circuit GHZ à topologie en étoile, `lookahead` tend à produire la profondeur d’ 2Q et le nombre de portes les plus faibles parmi les heuristiques SABRE, car sa fonction de coût prospective est bien adaptée aux circuits présentant des schémas de connectivité à longue portée. `StarPreRouting`, Cependant, cette méthode surpasse largement les trois autres : en réécrivant l'étoile sous la forme d'une chaîne linéaire avant le routage, elle contourne complètement le problème de recherche et fournit un circuit que le reste du transpileur peut mapper sur un chemin linéaire avec un nombre minimal d'opérations SWAP supplémentaires.\n",
        "\n",
        "Cet avantage se répercute directement sur la fidélité matérielle. Une profondeur d’ 2Q s et un nombre de portes plus faibles ne se traduisent pas toujours directement par une fidélité plus élevée (les qubits physiques spécifiques utilisés par une configuration et leur étalonnage lors de l’exécution ont également leur importance), mais lorsque l’écart de profondeur est aussi important que celui qui sépare SABRE et `StarPreRouting` \\[ici], l’approche tenant compte de la structure l’emporte haut la main, car le circuit accumule bien moins de décohérence et bien moins d’événements d’erreur à deux qubits. Le diagramme à barres de fidélité montre `StarPreRouting` que devance largement même la meilleure heuristique SABRE, tandis que `basic` se situe bien en dessous des autres, car ses circuits, beaucoup plus complexes, sont à l'origine de la plupart des erreurs.\n",
        "\n",
        "**Principaux intérêts :**\n",
        "\n",
        "* Parmi les algorithmes heuristiques SABRE, `decay` et `lookahead` sont nettement plus performants que `basic` pour les circuits non triviaux. Pour les charges de travail de production, privilégiez l'une des deux options.\n",
        "* Le meilleur algorithme heuristique SABRE dépend de votre circuit et de votre matériel. Tester plusieurs heuristiques avec plusieurs graines constitue la stratégie la plus fiable.\n",
        "* Si vous souhaitez explorer encore davantage de configurations, augmentez `swap_trials` (et `layout_trials` lorsque vous n'effectuez pas de passage de routage personnalisé) plutôt que de répartir la charge de travail vers des nœuds distants. Les passes SABRE parallélisent déjà les essais entre les threads locaux, et la charge de travail par essai est suffisamment faible pour que la surcharge liée à la distribution l'emporte généralement sur tout gain de vitesse.\n",
        "* Lorsque le circuit présente une structure particulière connue, l'application d'une passe tenant compte de la structure, comme celle `StarPreRouting` effectuée avant SABRE, peut permettre d'obtenir une amélioration d'un ordre de grandeur qu'aucun réglage de SABRE ne pourra égaler. Cela ne remplace pas SABRE : `StarPreRouting` cela n'est utile que lorsque le circuit comporte effectivement des sous-circuits en étoile et que le backend dispose d'un chemin linéaire suffisamment long. Lorsque vous connaissez la forme de votre circuit, il est utile de vérifier s'il existe des correspondances dans la bibliothèque de passes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65c2cebe-50b9-4304-9123-bf4cea7ecff6",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Etapes suivantes\n",
        "\n",
        "Si ce travail vous a paru intéressant, les documents suivants pourraient vous intéresser :\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommandations\">\n",
        "  * [`SabreLayout` Référence API](/docs/api/qiskit/qiskit.transpiler.passes.SabreLayout) : documentation complète sur les paramètres\n",
        "  * [Article sur SABRE](https://arxiv.org/abs/1809.02573) : l'algorithme SABRE original pour la disposition et le routage\n",
        "  * [LightSABRE article](https://arxiv.org/abs/2409.08368) : les améliorations algorithmiques qui sous-tendent l'implémentation actuelle de SABRE par Qiskit\n",
        "  * [Écrivez une étape de transpilation personnalisée](/docs/guides/custom-transpiler-pass) : créez votre propre logique de transpilation\n",
        "  * [Plug-ins de transpilation](/docs/guides/transpiler-plugins) : étendre le pipeline de transpilation de Qiskit avec des passes tierces\n",
        "  * [Représentation DAG](/docs/guides/DAG-representation) : comprendre le graphe acyclique orienté utilisé en interne par le transpileur\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42d1c053-1683-4c32-be1b-a36602207f74",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## Enquête tutorielle\n",
        "\n",
        "Veuillez répondre à cette courte enquête pour nous faire part de vos commentaires sur ce didacticiel. Vos commentaires nous aideront à améliorer nos offres de contenu et l'expérience des utilisateurs.\n",
        "\n",
        "[Lien vers l'enquête](https://your.feedback.ibm.com/jfe/form/SV_d9YWUSQIAvU9HXE)\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"
    },
    "hours": 1,
    "qpuSeconds": 60
  },
  "nbformat": 4,
  "nbformat_minor": 4
}