{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "1ed79d34-1831-47cb-9985-5bb7dacc70e0",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Optimización de la transpilación con SABRE\"\n",
        "description: \"Optimizar circuitos cuánticos utilizando el diseño y el enrutamiento de SABRE para su ejecución en hardware a gran escala.\"\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",
        "# Optimización de la transpilación con SABRE\n",
        "\n",
        "*Estimación de tiempo de ejecución: 1 minuto en un procesador Heron r2 (NOTA: Se trata únicamente de una estimación. (El tiempo de ejecución puede variar.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d6834016-1525-42d7-aa21-0fef9d957ecd",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados del aprendizaje\n",
        "\n",
        "Una vez que hayas completado este tutorial, deberías haber comprendido lo siguiente:\n",
        "\n",
        "* Cómo configurar los parámetros de SABRE (`layout_trials`, `swap_trials`, `max_iterations`) para mejorar la calidad de la transpilación\n",
        "* Las relaciones de compensación entre el tiempo de ejecución de la transpilación y la calidad del circuito (profundidad y número de puertas)\n",
        "* Cómo personalizar la heurística de enrutamiento de SABRE (`basic`, `decay`, `lookahead`) y comparar su rendimiento en hardware\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Requisitos previos\n",
        "\n",
        "Te recomendamos que te familiarices con los siguientes temas antes de seguir este tutorial:\n",
        "\n",
        "* [Circuitos de transpilación](/docs/guides/transpile) : visión general de la transpilación en Qiskit\n",
        "* [Etapas del transpilador](/docs/guides/transpiler-stages) : etapas de disposición y enrutamiento\n",
        "* [Configurar gestores de pasadas predefinidos](/docs/guides/transpile-with-pass-managers) : personalización de los niveles de optimización\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "La transpilación convierte los circuitos cuánticos en formatos compatibles con un hardware cuántico específico. Las dos etapas clave son la elección de una **disposición de qubits** (asignación de qubits lógicos a qubits físicos) y **el enrutamiento de puertas** (inserción de puertas SWAP para que las puertas de múltiples qubits respeten la conectividad del dispositivo).\n",
        "\n",
        "**SABRE** ( *algoritmo de búsqueda heurística bidireccional basado en SWAP* ) optimiza tanto la disposición como el enrutamiento. Resulta especialmente eficaz para circuitos a gran escala (más de 100 qubits) en dispositivos con mapas de acoplamiento complejos, como los procesadores Heron de IBM®. SABRE minimiza las puertas SWAP y reduce la profundidad del circuito, lo que mejora la fidelidad de ejecución. Las recientes mejoras en el algoritmo « [LightSABRE](https://arxiv.org/abs/2409.08368) » reducen aún más los tiempos de ejecución y el número de puertas lógicas.\n",
        "\n",
        "En este tutorial, primero configurarás el programa `SabreLayout` con diferentes parámetros para optimizar un pequeño circuito GHZ y observarás cómo afecta esto a la fidelidad de la ejecución. A continuación, compararás las heurísticas de enrutamiento de SABRE a gran escala en hardware real.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50cf9d9f-c875-49d5-83b0-c691363615ab",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de tener instalado lo siguiente:\n",
        "\n",
        "* Qiskit SDK v2.0 o posterior, con soporte [para visualización](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.22 o posterior (`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",
        "## Configuración\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",
        "## Ejemplo de simulador a pequeña escala\n",
        "\n",
        "En esta sección se utiliza un **simulador con ruido**, basado en el modelo de ruido del backend real, para demostrar cómo las diferentes `SabreLayout` configuraciones afectan tanto a la calidad de la transpilación como a la fidelidad de la ejecución. El uso de `qiskit_aer` con un modelo de ruido derivado de datos reales de calibración del hardware te permite probar la transpilación sin consumir créditos de hardware.\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",
        "### Paso 1: Asignar entradas clásicas a un problema cuántico\n",
        "\n",
        "Construimos un **circuito GHZ** con topología en estrella de 15 qubits. El primer qubit es el nodo central, y las puertas CNOT lo conectan directamente con todos los demás qubits. Esta topología plantea un problema de diseño complejo, ya que no se corresponde de forma sencilla con el mapa de acoplamiento del dispositivo.\n",
        "\n",
        "También definimos `ZZ` operadores para medir las correlaciones de entrelazamiento $\\langle Z_0 Z_i \\rangle$ entre pares 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=\"Cuando conoces la estructura del circuito\">\n",
        "  SABRE es un algoritmo de uso general y no parte de ningún supuesto sobre la estructura del circuito. Para este circuito GHZ con topología en estrella, se conoce de hecho un enrutamiento óptimo: el [`StarPreRouting`](/docs/api/qiskit/qiskit.transpiler.passes.StarPreRouting) «pass» detecta los subcircuitos en estrella y los reescribe en una cadena lineal que se adapta directamente a cualquier backend con una ruta lineal lo suficientemente larga. Este tutorial se centra en SABRE porque funciona con circuitos arbitrarios, pero si sabes que tu circuito tiene una estructura especial bien definida, aplicar una pasada especializada antes `StarPreRouting` del enrutamiento puede ofrecer mejores resultados que cualquier búsqueda heurística.\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",
        "### Paso 2: Optimizar el problema para la ejecución en hardware cuántico\n",
        "\n",
        "El gestor de pasadas predefinido `optimization_level=3` ya utiliza `SabreLayout`, pero con valores predeterminados conservadores. Para analizar el impacto de unos parámetros más estrictos, esa pasada se sustituye por una personalizada `SabreLayout` , configurada para realizar una búsqueda más exhaustiva, mientras que el resto de pasadas de la fase de maquetación no se modifican. A modo de comparación adicional, un cuarto gestor de pasadas mantiene la configuración predeterminada `SabreLayout` , pero añade [`StarPreRouting`](/docs/api/qiskit/qiskit.transpiler.passes.StarPreRouting) a la fase de inicialización. `StarPreRouting` es una pasada *que tiene en cuenta la estructura,* que detecta los subcircuitos en forma de estrella y los reescribe en una cadena lineal antes del enrutamiento.\n",
        "\n",
        "El proceso es el siguiente:\n",
        "\n",
        "1. **Revisa** el gestor de pases predeterminado para ver dónde `SabreLayout` se encuentra dentro de la `layout` etapa.\n",
        "2. **Sustituye** esa llamada por una instancia personalizada `SabreLayout` utilizando `PassManager.replace(index, passes=...)`, y compila la `pm_star` variante con `pm.init += StarPreRouting()`.\n",
        "3. **Ejecuta** los cuatro gestores de pases y compara las métricas.\n",
        "\n",
        "Las cuatro configuraciones son:\n",
        "\n",
        "| Configurar                    | Descripción                                                                                                          |\n",
        "| ----------------------------- | -------------------------------------------------------------------------------------------------------------------- |\n",
        "| `pm_1` (Valor predeterminado) | Preajuste predeterminado de « level-3 » (`SabreLayout` con `max_iterations=4`, `layout_trials=20`, `swap_trials=20`) |\n",
        "| `pm_2`                        | Personalizado `SabreLayout` (`max_iterations=4`, `layout_trials=200`, `swap_trials=200`)                             |\n",
        "| `pm_3`                        | Personalizado `SabreLayout` (`max_iterations=8`, `layout_trials=200`, `swap_trials=200`)                             |\n",
        "| `pm_star`                     | Configuración predeterminada con `StarPreRouting` añadido a la fase de inicialización                                |\n",
        "\n",
        "**Parámetros clave de SABRE:**\n",
        "\n",
        "* **`layout_trials` / `swap_trials`**: Controla el número de diseños y soluciones de enrutamiento que explora SABRE. Al aumentar el número de pruebas, SABRE explora un espacio de búsqueda más amplio, lo que aumenta las posibilidades de encontrar una solución mejor.\n",
        "* **`max_iterations`**: Determina el número de ciclos de refinamiento de enrutamiento hacia adelante y hacia atrás que SABRE realiza con cada candidato. SABRE mejora el diseño de forma iterativa aprendiendo de la información obtenida del enrutamiento, por lo que cuantas más iteraciones haya, mejores serán las mejoras.\n",
        "\n",
        "Ambas opciones conllevan un mayor tiempo de transpilación, pero los circuitos resultantes son más cortos y utilizan menos puertas lógicas, lo que reduce directamente la decoherencia y los errores de las puertas lógicas en el hardware real.\n",
        "\n",
        "**Paso 2a: : Revisa el gestor de contraseñas predeterminado.** A `StagedPassManager` está compuesto por etapas (`init`, `layout`, `routing`, `translation`, `optimization`, `scheduling`), cada una de las cuales es a su vez un `PassManager`. Al llamar `.draw()` a una función en un escenario, esta representa sus pasos en forma de gráfico, lo que nos permite ver dónde `SabreLayout` se encuentra.\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": [
        "En el diagrama anterior, el `SabreLayout` paso que queremos personalizar se encuentra en la `ConditionalController` posición **\\[2]** de la etapa de maquetación. Ese controlador hace dos cosas:\n",
        "\n",
        "* Se activa `SabreLayout` de forma selectiva, por lo que solo se ejecuta cuando `VF2Layout`\\[ no] se ha podido encontrar una correspondencia perfecta (en caso contrario, se mantiene la disposición perfecta de « VF2 »).\n",
        "* Se inicia `SabreLayout` con una `BarrierBeforeFinalMeasurements` pasada que evita que las mediciones se reordenen durante el enrutamiento interno de SabreLayout's.\n",
        "\n",
        "Si simplemente `replace(index=2, passes=sl_2)`, ambos comportamientos se descartan. Para conservarlos, volvemos a envolver nuestro producto personalizado `SabreLayout` en el mismo material `ConditionalController` (en las mismas condiciones y con la misma barrera protectora) antes de colocarlo en su sitio.\n",
        "\n",
        "**Paso 2b: : Crea pases personalizados `SabreLayout` y sustituye los predeterminados.**\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 posición **\\[2]** vuelve a ser un `ConditionalController` — de forma idéntica a la predeterminada, pero el interior `SabreLayout` es el que hemos definido nosotros (con `layout_trials=200`, `swap_trials=200`, y `max_iterations=8` para `pm_3`; `pm_2` es idéntico salvo por `max_iterations=4`). La barrera protectora y el `_vf2_match_not_found` sistema de control de acceso se mantienen, por lo que la única diferencia entre `pm_2`/`pm_3` y `pm_1` es la propia configuración de SABRE. `pm_star` mantiene la configuración predeterminada `SabreLayout` y solo añade `StarPreRouting` al final de la fase de inicialización.\n",
        "\n",
        "**Paso 2c: : Ejecuta cada gestor de pasadas y compáralos.**\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": [
        "Los tres gestores de pasadas modificados generaron circuitos con una profundidad de « 2Q » menor que la predeterminada. Las configuraciones agresivas de SABRE (`pm_2` y `pm_3`) sacrifican un mayor tiempo de transpilación a cambio de una búsqueda más amplia, mientras que `pm_star` aprovecha la estructura en estrella del circuito y produce un resultado aún menos profundo sin incurrir en ningún coste adicional de transpilación. Los resultados exactos variarán de una ejecución a otra, pero la tendencia general es constante: un mayor número de pruebas e iteraciones de SABRE permite a la búsqueda heurística abarcar un espacio más amplio, y las pasadas que tienen en cuenta la estructura, como `StarPreRouting` , pueden evitar por completo esa búsqueda cuando la forma del circuito coincide.\n",
        "\n",
        "Incluso a esta escala tan reducida (15 qubits), el margen de mejora es tal que los tres enfoques superan al método predeterminado. En el caso de circuitos más grandes (más de 100 qubits), el espacio de búsqueda aumenta de forma espectacular y las ventajas tanto del mayor número de intentos como de las pasadas que tienen en cuenta la estructura se hacen mucho más evidentes, tal y como se verá en la sección dedicada a los circuitos a gran escala.\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",
        "### Paso 3: Ejecutar utilizando Qiskit primitives\n",
        "\n",
        "Ejecutamos cada circuito transpilado **10 veces** utilizando Aer `EstimatorV2` con un modelo de ruido derivado del backend real. Dado que los resultados de las simulaciones, que presentan ruido, varían de una ejecución a otra, calcular la media de varias ejecuciones ofrece estimaciones de la fidelidad más fiables y nos permite cuantificar la incertidumbre estadística mediante barras de error.\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": [
        "Al tratarse de un circuito pequeño, los valores de fidelidad son bastante similares en las cuatro configuraciones. Los circuitos son lo suficientemente cortos como para que el ruido del hardware no afecte de forma significativa ni siquiera a la versión menos optimizada. La fidelidad media sigue, en líneas generales, la profundidad de 2Q : `pm_3` y `pm_star`, los dos circuitos menos profundos, alcanzan las fidelidades más altas y están prácticamente empatados dentro de sus barras de error. `pm_2` es un contraejemplo útil: aunque su profundidad de « 2Q » es menor que `pm_1`la de, su fidelidad media también resulta ser ligeramente inferior, lo que nos recuerda que la relación entre profundidad y fidelidad es de carácter estadístico y no determinista. También son importantes los qubits concretos que selecciona una configuración y la calibración de dichos qubits en tiempo de ejecución.\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",
        "### Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado\n",
        "\n",
        "A continuación, representa gráficamente las correlaciones de entrelazamiento $\\langle Z_0 Z_i \\rangle$ en función de la distancia entre los qubits, junto con la **correlación media** como única métrica de fidelidad. En un caso ideal (sin ruido), todas las correlaciones serían 1. Dado que el ruido es realista, cada puerta adicional introduce un error y cada paso de tiempo adicional permite la decoherencia, por lo que un circuito transpilado con menor profundidad y menos puertas (especialmente puertas de dos qubits) debería conservar mejor el entrelazamiento.\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": [
        "Los resultados muestran una clara relación entre la calidad de la transpilación y la fidelidad de la ejecución, con algunas salvedades que conviene tener en cuenta:\n",
        "\n",
        "* **`pm_1` (por defecto)** : Línea de base. Con solo 20 pruebas y cuatro iteraciones, SABRE tiene un margen limitado para la optimización, lo que da como resultado el circuito más profundo de entre los generados exclusivamente por SABRE.\n",
        "* **`pm_2` (más pruebas)** : Al explorar diez veces más candidatos se encuentra una disposición ligeramente menos profunda, pero la fidelidad media se mantiene prácticamente estable (e incluso puede descender por debajo del valor de referencia debido al ruido), ya que la ganancia en profundidad es pequeña a esta escala.\n",
        "* **`pm_3` (más pruebas + más iteraciones)** : Al duplicar `max_iterations` el número a 8, SABRE dispone de más ciclos de refinamiento, lo que da como resultado el circuito más superficial creado exclusivamente con SABRE y la fidelidad media más alta de la comparación.\n",
        "* **`pm_star` (predeterminado + StarPreRouting )** : Añade `StarPreRouting` a la fase de inicialización de un preajuste que, por lo demás, sería el predeterminado. La reescritura que tiene en cuenta la estructura reduce el símbolo «\\*» a una cadena lineal que el resto del transpilador asigna a la ruta lineal del dispositivo, lo que da como resultado el circuito de menor profundidad en general (ligeramente mejor que `pm_3`) y una fidelidad equivalente `pm_3` a dentro de los márgenes de error. Lo hace en el mismo tiempo de transpilación que la opción predeterminada, ya que la reescritura es prácticamente gratuita en comparación con la búsqueda estocástica de SABRE.\n",
        "\n",
        "Ten en cuenta que aumentar `max_iterations` no siempre tiene un impacto positivo. En este caso resultó de gran ayuda, pero en otros circuitos o backends es posible que las iteraciones adicionales no aporten más mejoras, o que incluso perjudiquen ligeramente el rendimiento debido a una optimización excesiva de un mínimo local. En general, deberías aumentar `layout_trials` y `swap_trials` tanto como te lo permita el tiempo del que dispongas, ya que un mayor número de intentos siempre aumenta las posibilidades de encontrar una disposición mejor. Merece la pena probar el aumento `max_iterations` , pero debe validarse para tu caso de uso concreto. Los pases especializados como `StarPreRouting` tienen un espíritu similar, pero dependen más del circuito: solo son útiles cuando el circuito contiene realmente la estructura a la que se dirigen. La ganancia es elevada cuando procede y, en caso contrario, es nula, pero probarlas no cuesta prácticamente nada.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c2b60e42-4aa2-4dc1-8a11-022072e79776",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Ejemplo de hardware a gran escala\n",
        "\n",
        "Además de ajustar el número de intentos, SABRE permite personalizar la **heurística de enrutamiento**. SABRE ofrece tres heurísticas:\n",
        "\n",
        "* **`basic`**: Un método codicioso sencillo que selecciona el intercambio que minimiza la distancia inmediata a la siguiente puerta.\n",
        "* **`decay`** (por defecto): Ponderiza dinámicamente los qubits en función de la actividad reciente, lo que desalienta los intercambios repetidos en los mismos qubits.\n",
        "* **`lookahead`**: Evalúa los costes de enrutamiento futuros analizando las próximas puertas, lo que podría permitir encontrar mejores secuencias de intercambio.\n",
        "\n",
        "Para utilizar una heurística personalizada, crea un `SabreSwap` «pass» y conéctalo a `SabreLayout` mediante el `routing_pass` parámetro.\n",
        "\n",
        "`SabreLayout`Se añade un cuarto gestor de pasadas a la comparación: `pm_star_hw`, que mantiene la configuración predeterminada,`SabreSwap` pero añade `StarPreRouting` a la fase de inicialización. A esta escala (100 qubits), la búsqueda SABRE resulta más compleja, y la reescritura de una cadena en estrella a una cadena lineal supone una clara ventaja, ya que un procesador Heron cuenta con rutas lineales lo suficientemente largas como para albergar el circuito resultante.\n",
        "\n",
        "Aquí comparamos las tres heurísticas de SABRE, además de `StarPreRouting` a gran escala, en un circuito GHZ de 100 qubits. Realizamos varias pruebas de diseño con diferentes semillas para las configuraciones de SABRE, seleccionamos el mejor circuito transpilado de cada una de ellas y los enviamos todos al hardware real junto con el `StarPreRouting` resultado.\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",
        "### Pasos 1 a 4 comprimidos en un único bloque de código\n",
        "\n",
        "Aquí se presenta el flujo de trabajo completo a mayor escala. Cuando se utiliza `SabreSwap` como para `routing_pass` `SabreLayout`, solo se realiza un intento de disposición por llamada, por lo que la siguiente celda de código recorre las semillas para explorar el espacio de disposición.\n",
        "\n",
        "Utilizamos el mismo `wrap_sabre` ayudante definido en el paso 2 a pequeña escala (más arriba) y añadimos un ayudante análogo `wrap_routing` , ya que la `routing` etapa en el índice \\[1] también es un `ConditionalController([BarrierBeforeFinalMeasurements, routing_pass], ...)` —; si la sustituyéramos sin más, se eliminaría igualmente la barrera protectora y la `_swap_condition` activación.\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",
        "### Análisis\n",
        "\n",
        "Los diagramas de dispersión muestran una variabilidad significativa entre las semillas para las tres heurísticas de SABRE, lo que pone de relieve la importancia de realizar múltiples pruebas de diseño en lugar de basarse en una única transpilación. La `StarPreRouting` línea se mantiene prácticamente plana en todas las semillas, ya que la reescritura de una estructura en forma de estrella a una cadena lineal es determinista dada la estructura; el enrutamiento posterior de SABRE tiene entonces muy poca libertad en una cadena lineal, por lo que la semilla casi no influye en la profundidad o el tamaño finales.\n",
        "\n",
        "Según los resultados de la transpilación, tanto la `decay` heurística como `lookahead` la superan sistemáticamente a `basic` por un amplio margen. La `basic` heurística, aunque rápida, utiliza una estrategia codiciosa sencilla que a menudo da lugar a circuitos considerablemente más profundos. En el caso de este circuito GHZ con topología en estrella, `lookahead` tiende a generar la menor profundidad de « 2Q » y el menor número de puertas entre las heurísticas SABRE, ya que su función de coste prospectiva se adapta bien a circuitos con patrones de conectividad de largo alcance. `StarPreRouting`, Sin embargo, supera a los tres por un margen considerable: al reescribir la estrella en una cadena lineal antes del enrutamiento, elude por completo el problema de búsqueda y genera un circuito que el resto del transpilador puede mapear en una ruta lineal con un número mínimo de SWAP adicionales.\n",
        "\n",
        "Esa ventaja se refleja directamente en la fidelidad del hardware. Una menor profundidad de « 2Q » y un menor número de puertas no siempre se traducen directamente en una mayor fidelidad (también influyen los qubits físicos específicos que utiliza un diseño y su calibración en tiempo de ejecución), pero cuando la diferencia de profundidad es tan grande como la que existe entre SABRE y `StarPreRouting` este caso, el enfoque que tiene en cuenta la estructura gana de forma contundente, ya que el circuito acumula mucha menos decoherencia y muchos menos eventos de error de dos qubits. El gráfico de barras de fidelidad muestra `StarPreRouting` que se sitúa muy por delante incluso de la mejor heurística de SABRE, mientras que `basic` se encuentra muy por debajo del resto, ya que sus circuitos, mucho más complejos, acumulan la mayor parte del error.\n",
        "\n",
        "**Puntos clave:**\n",
        "\n",
        "* Entre las heurísticas de SABRE, `decay` y `lookahead` son considerablemente mejores que `basic` para circuitos no triviales. Elige una de las dos opciones para las cargas de trabajo de producción.\n",
        "* La mejor heurística SABRE depende de tu circuito y de tu hardware. Probar varias heurísticas con varias semillas es la estrategia más fiable.\n",
        "* Si quieres explorar aún más configuraciones, aumenta `swap_trials` (y `layout_trials` cuando no estés asignando una ruta personalizada) en lugar de distribuir el trabajo entre nodos remotos. Los pases de SABRE ya paralelizan las pruebas entre subprocesos locales, y el trabajo por prueba es tan reducido que la sobrecarga de la distribución suele superar cualquier aumento de velocidad.\n",
        "* Cuando el circuito tiene una estructura especial conocida, aplicar una pasada que tenga en cuenta dicha estructura —como `StarPreRouting` antes de SABRE— puede suponer una mejora de un orden de magnitud que ningún ajuste de SABRE podrá igualar. Esto no sustituye a SABRE: `StarPreRouting` solo resulta útil cuando el circuito contiene realmente subcircuitos en estrella y el backend tiene una trayectoria lineal lo suficientemente larga. Merece la pena consultar la biblioteca de pases para ver si hay coincidencias siempre que conozcas la forma de tu circuito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65c2cebe-50b9-4304-9123-bf4cea7ecff6",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximos pasos\n",
        "\n",
        "Si este trabajo te ha parecido interesante, quizá te interese el siguiente material:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendaciones\">\n",
        "  * [`SabreLayout` Referencia de la API](/docs/api/qiskit/qiskit.transpiler.passes.SabreLayout) : documentación completa de los parámetros\n",
        "  * [Artículo sobre SABRE](https://arxiv.org/abs/1809.02573) : el algoritmo SABRE original para el diseño y el enrutamiento\n",
        "  * [LightSABRE artículo](https://arxiv.org/abs/2409.08368) : las mejoras algorítmicas que sustentan la implementación actual de SABRE en Qiskit\n",
        "  * [Escribe una pasada de transpilador personalizada](/docs/guides/custom-transpiler-pass) : crea tu propia lógica de transpilación\n",
        "  * [Complementos de transpilador](/docs/guides/transpiler-plugins) : amplía el proceso de transpilación de Qiskit con pases de terceros\n",
        "  * [Representación DAG](/docs/guides/DAG-representation) : comprender el grafo acíclico dirigido que utiliza internamente el transpilador\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42d1c053-1683-4c32-be1b-a36602207f74",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## Encuesta tutorial\n",
        "\n",
        "Responda a esta breve encuesta para darnos su opinión sobre este tutorial. Su opinión nos ayudará a mejorar nuestra oferta de contenidos y la experiencia de los usuarios.\n",
        "\n",
        "[Enlace a la encuesta](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
}