{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "1ed79d34-1831-47cb-9985-5bb7dacc70e0",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Ottimizzazione della transpilazione con SABRE\"\n",
        "description: \"Ottimizzare i circuiti quantistici utilizzando il layout e il routing SABRE per l'esecuzione su hardware su larga scala.\"\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",
        "# Ottimizzazione della transpilazione con SABRE\n",
        "\n",
        "*Stima del tempo di esecuzione: 1 minuto su un processore Heron r2 (NOTA: si tratta solo di una stima. (La durata potrebbe variare.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d6834016-1525-42d7-aa21-0fef9d957ecd",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "Dopo aver seguito questo tutorial, dovresti aver compreso:\n",
        "\n",
        "* Come configurare i parametri SABRE (`layout_trials`, `swap_trials`, `max_iterations`) per migliorare la qualità della transpilazione\n",
        "* I compromessi tra il tempo di esecuzione della transpilazione e la qualità del circuito (profondità e numero di porte)\n",
        "* Come personalizzare l'euristica di instradamento SABRE (`basic`, `decay`, `lookahead`) e confrontarne le prestazioni su hardware\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prerequisiti\n",
        "\n",
        "Ti consigliamo di acquisire familiarità con i seguenti argomenti prima di seguire questo tutorial:\n",
        "\n",
        "* [Circuiti di transpile](/docs/guides/transpile) : panoramica sulla transpile in Qiskit\n",
        "* [Fasi del transpiler](/docs/guides/transpiler-stages) : fasi di layout e di instradamento\n",
        "* [Configurazione dei gestori di passaggi predefiniti](/docs/guides/transpile-with-pass-managers) : personalizzazione dei livelli di ottimizzazione\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "La transpilazione converte i circuiti quantistici in forme compatibili con specifici dispositivi quantistici. Due fasi fondamentali sono la scelta della **configurazione dei qubit** (mappatura dei qubit logici sui qubit fisici) e **l'instradamento dei gate** (inserimento di gate SWAP affinché i gate multi-qubit rispettino la connettività del dispositivo).\n",
        "\n",
        "**SABRE** ( *algoritmo di ricerca euristica bidirezionale basato su SWAP* ) ottimizza sia il layout che l'instradamento. È particolarmente efficace per circuiti su larga scala (oltre 100 qubit) su dispositivi con mappe di accoppiamento complesse, come i processori Heron dell' IBM®. SABRE riduce al minimo i gate SWAP e diminuisce la profondità del circuito, migliorando la fedeltà di esecuzione. I recenti miglioramenti apportati all'algoritmo \" [LightSABRE](https://arxiv.org/abs/2409.08368) \" riducono ulteriormente i tempi di esecuzione e il numero di porte logiche.\n",
        "\n",
        "In questo tutorial, configurerai innanzitutto il circuito `SabreLayout` con diversi parametri per ottimizzare un piccolo circuito GHZ e osserverai l'impatto sulla fedeltà di esecuzione. Successivamente, confronterai le euristiche di instradamento di SABRE su larga scala su hardware reale.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50cf9d9f-c875-49d5-83b0-c691363615ab",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:\n",
        "\n",
        "* Qiskit SDK v2.0 o versioni successive, con supporto [alla visualizzazione](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.22 o versioni successive (`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",
        "## Configura\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",
        "## Esempio di simulatore su piccola scala\n",
        "\n",
        "In questa sezione viene utilizzato un **simulatore con rumore**, basato sul modello di rumore del backend reale, per dimostrare in che modo le diverse `SabreLayout` configurazioni influenzano sia la qualità della transpilazione sia la fedeltà di esecuzione. L'utilizzo `qiskit_aer` di con un modello di rumore derivato dai dati effettivi di calibrazione dell'hardware consente di testare la transpilazione senza consumare crediti 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",
        "### Fase 1: mappare gli input classici su un problema quantistico\n",
        "\n",
        "Costruiamo un **circuito GHZ a topologia a stella** con 15 qubit. Il primo qubit funge da nodo centrale, con porte CNOT che lo collegano direttamente a tutti gli altri qubit. Questa topologia pone un problema complesso di layout, poiché non si corrisponde in modo banale alla mappa di accoppiamento del dispositivo.\n",
        "\n",
        "Definiamo `ZZ` inoltre degli operatori per misurare le correlazioni di entanglement $\\langle Z_0 Z_i \\rangle$ tra coppie di qubit.\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=\"Quando si conosce la struttura del circuito\">\n",
        "  SABRE è un algoritmo generico e non formula ipotesi sulla struttura del circuito. Per questo circuito GHZ a topologia a stella, è infatti nota una soluzione ottimale di instradamento: il [`StarPreRouting`](/docs/api/qiskit/qiskit.transpiler.passes.StarPreRouting) pass individua i sottocircuiti a stella e li riscrive in una catena lineare che si mappa direttamente su qualsiasi backend dotato di un percorso lineare sufficientemente lungo. Questo tutorial si concentra su SABRE perché funziona con circuiti arbitrari, ma se sai che il tuo circuito presenta una struttura particolare ben definita, l'applicazione di `StarPreRouting` un passaggio specializzato prima del routing può dare risultati migliori rispetto a qualsiasi ricerca euristica.\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",
        "### Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico\n",
        "\n",
        "`SabreLayout`Il gestore dei passaggi predefinito `optimization_level=3` utilizza già questa impostazione, ma con valori predefiniti prudenti. Per valutare l'impatto di impostazioni più rigorose, quel passaggio viene sostituito con uno personalizzato `SabreLayout` e configurato per una ricerca più aggressiva, mentre tutti gli altri passaggi della fase di layout rimangono invariati. A titolo di confronto, un quarto gestore di passaggi mantiene l'impostazione predefinita `SabreLayout` , ma aggiunge [`StarPreRouting`](/docs/api/qiskit/qiskit.transpiler.passes.StarPreRouting) alla fase di inizializzazione. `StarPreRouting` è un passaggio che *tiene conto della struttura, in grado di* individuare i sottocircuiti a stella e di riscriverli in una catena lineare prima dell'instradamento.\n",
        "\n",
        "Il flusso di lavoro è il seguente:\n",
        "\n",
        "1. **Esamina** il gestore dei passaggi predefinito per vedere dove `SabreLayout` si trova all’interno dello `layout` stage.\n",
        "2. **Sostituisci** quel pass con un'istanza personalizzata `SabreLayout` utilizzando `PassManager.replace(index, passes=...)`, e crea la `pm_star` variante con `pm.init += StarPreRouting()`.\n",
        "3. **Esegui** tutti e quattro i gestori di passaggi e confronta le metriche.\n",
        "\n",
        "Le quattro configurazioni sono:\n",
        "\n",
        "| Configurazione       | Descrizione                                                                                                         |\n",
        "| -------------------- | ------------------------------------------------------------------------------------------------------------------- |\n",
        "| `pm_1` (predefinito) | Preimpostazione predefinita di level-3 (`SabreLayout` con `max_iterations=4`, `layout_trials=20`, `swap_trials=20`) |\n",
        "| `pm_2`               | Personalizzato `SabreLayout` (`max_iterations=4`, `layout_trials=200`, `swap_trials=200`)                           |\n",
        "| `pm_3`               | Personalizzato `SabreLayout` (`max_iterations=8`, `layout_trials=200`, `swap_trials=200`)                           |\n",
        "| `pm_star`            | Preimpostazione predefinita con `StarPreRouting` aggiunto alla fase di inizializzazione                             |\n",
        "\n",
        "**Parametri chiave di SABRE:**\n",
        "\n",
        "* **`layout_trials` / `swap_trials`**: Consente di controllare il numero di layout e soluzioni di instradamento che SABRE prende in considerazione. Aumentando il numero di prove, SABRE esplora uno spazio di ricerca più ampio, aumentando così le possibilità di trovare una soluzione migliore.\n",
        "* **`max_iterations`**: Determina il numero di cicli di affinamento del routing avanti-indietro che SABRE esegue su ciascun candidato. SABRE ottimizza il layout in modo iterativo, imparando dai risultati del routing; pertanto, maggiore è il numero di iterazioni, migliori saranno i risultati.\n",
        "\n",
        "Entrambe le soluzioni comportano tempi di transpilazione più lunghi, ma i circuiti risultanti sono più brevi e utilizzano un numero minore di porte logiche, il che riduce direttamente la decoerenza e gli errori delle porte logiche sull'hardware reale.\n",
        "\n",
        "**Passo 2a: Verificare il gestore delle password predefinito.** A `StagedPassManager` è composto da stadi (`init`, `layout`, `routing`, `translation`, `optimization`, `scheduling`), ciascuno dei quali è a sua volta un `PassManager`. Quando si chiama `.draw()` una funzione su uno stage, i suoi passaggi vengono rappresentati sotto forma di grafico, così possiamo vedere dove `SabreLayout` si trova.\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": [
        "Nel diagramma sopra riportato, il `SabreLayout` passaggio che vogliamo personalizzare si trova nella `ConditionalController` posizione **\\[2]** della fase di layout. Quel controller svolge due funzioni:\n",
        "\n",
        "* Funziona in `SabreLayout` modo tale da attivarsi solo quando `VF2Layout`\\[ non] è stato possibile trovare una mappatura perfetta (altrimenti viene mantenuta la disposizione perfetta VF2 ).\n",
        "* È preceduto `SabreLayout` da un `BarrierBeforeFinalMeasurements` passaggio che impedisce che le misurazioni vengano riordinate durante l'instradamento interno di SabreLayout's.\n",
        "\n",
        "Se ci limitiamo a `replace(index=2, passes=sl_2)`, entrambi i comportamenti vengono eliminati. Per conservarli, riavvolgiamo il nostro prodotto personalizzato `SabreLayout` nello stesso `ConditionalController` imballaggio (nelle stesse condizioni e con la stessa barriera protettiva) prima di sostituirlo.\n",
        "\n",
        "**Passaggio 2b: : creare pass personalizzati `SabreLayout` e sostituire quelli predefiniti.**\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 posizione **\\[2]** ora è di nuovo un `ConditionalController` — identico nella forma a quello predefinito, ma il interno `SabreLayout` è quello personalizzato (con `layout_trials=200`, `swap_trials=200`, e `max_iterations=8` per `pm_3`; `pm_2` è identico tranne che per `max_iterations=4`). La barriera protettiva e il sistema `_vf2_match_not_found` di gating vengono mantenuti, quindi l'unica differenza tra `pm_2`/`pm_3` e `pm_1` è la configurazione SABRE stessa. `pm_star` mantiene l'impostazione predefinita `SabreLayout` e aggiunge `StarPreRouting` solo alla fine della fase di inizializzazione.\n",
        "\n",
        "**Passaggio 2c: : eseguire ciascun pass manager e confrontare i risultati.**\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": [
        "Tutti e tre i gestori di passaggi modificati hanno generato circuiti con una profondità di \" 2Q \" inferiore rispetto a quella predefinita. Le configurazioni SABRE aggressive (`pm_2` e `pm_3`) sacrificano un tempo di transpilazione più lungo a favore di una ricerca più ampia, mentre `pm_star` sfrutta la struttura a stella del circuito e produce un risultato ancora più superficiale senza comportare alcun costo aggiuntivo di transpilazione. I miglioramenti esatti variano da esecuzione a esecuzione, ma la tendenza generale è costante: un numero maggiore di prove e iterazioni SABRE consente alla ricerca euristica di esplorare uno spazio più ampio, mentre i passaggi che tengono conto della struttura, come `StarPreRouting` \\[...], possono aggirare completamente tale ricerca quando la forma del circuito corrisponde.\n",
        "\n",
        "Anche su questa scala ridotta (15 qubit), il margine di miglioramento è tale che tutti e tre gli approcci superano il risultato predefinito. Con circuiti più grandi (oltre 100 qubit), lo spazio di ricerca cresce in modo esponenziale e i vantaggi derivanti sia dall’aumento del numero di tentativi sia dai passaggi che tengono conto della struttura diventano molto più evidenti, come verrà illustrato nella sezione dedicata ai circuiti su larga scala.\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",
        "### Passaggio 3: eseguire utilizzando Qiskit primitives\n",
        "\n",
        "Eseguiamo ogni circuito transpilato **10 volte** utilizzando Aer `EstimatorV2` con un modello di rumore derivato dal backend reale. Poiché i risultati delle simulazioni, caratterizzati da una certa variabilità, variano da un'esecuzione all'altra, il calcolo della media su più esecuzioni fornisce stime di fedeltà più affidabili e ci consente di quantificare l'incertezza statistica mediante barre di errore.\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": [
        "Trattandosi di un circuito di piccole dimensioni, i valori di fedeltà risultano relativamente simili in tutte e quattro le configurazioni. I circuiti sono sufficientemente corti da impedire che il rumore dell'hardware penalizzi in modo significativo anche la versione meno ottimizzata. La fedeltà media riflette in linea di massima la profondità dell' 2Q : `pm_3` e `pm_star`, i due circuiti meno profondi, raggiungono i livelli di fedeltà più elevati e risultano sostanzialmente alla pari entro i rispettivi intervalli di errore. `pm_2` è un utile controesempio: sebbene la sua profondità di \" 2Q \" sia inferiore a `pm_1`quella di 's, anche la sua fedeltà media risulta leggermente inferiore, il che ci ricorda che il rapporto tra profondità e fedeltà è di natura statistica piuttosto che deterministica. Anche la scelta specifica dei qubit da parte di un layout e la loro calibrazione durante l'esecuzione sono fattori importanti.\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",
        "### Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato\n",
        "\n",
        "Successivamente, tracciare le correlazioni di entanglement $\\langle Z_0 Z_i \\rangle$ in funzione della distanza tra i qubit, insieme alla **correlazione** media come unico indicatore di fedeltà. In un caso ideale (privo di rumore), tutte le correlazioni sarebbero pari a 1. Considerando il rumore realistico, ogni porta aggiuntiva introduce un errore e ogni passo temporale aggiuntivo favorisce la decoerenza; pertanto, un circuito transpilato con profondità inferiore e un numero minore di porte (in particolare porte a due qubit) dovrebbe preservare meglio l’entanglement.\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": [
        "I risultati evidenziano una chiara correlazione tra la qualità della transpilazione e la fedeltà di esecuzione, con alcune precisazioni utili:\n",
        "\n",
        "* **`pm_1` (impostazione predefinita)** : Baseline. Con sole 20 prove e quattro iterazioni, SABRE ha un margine di ottimizzazione limitato, il che porta alla realizzazione del circuito più profondo tra quelli realizzati esclusivamente con SABRE.\n",
        "* **`pm_2` (ulteriori prove)** : Esplorando un numero di candidati dieci volte superiore si ottiene una struttura leggermente meno profonda, ma la fedeltà media rimane sostanzialmente invariata (e può persino scendere al di sotto del valore di riferimento a causa del rumore), poiché il guadagno in termini di profondità è esiguo a questa scala.\n",
        "* **`pm_3` (più prove + più iterazioni)** : Raddoppiando `max_iterations` il valore a 8, SABRE ottiene un maggior numero di cicli di affinamento, generando il circuito realizzato esclusivamente con SABRE meno profondo e la fedeltà media più elevata nel confronto.\n",
        "* **`pm_star` (impostazione predefinita + StarPreRouting )** : aggiunge `StarPreRouting` alla fase di inizializzazione di un preset che, per il resto, è quello predefinito. La riscrittura che tiene conto della struttura riduce il simbolo asterisco a una catena lineare che il resto del transpiler mappa sul percorso lineare del dispositivo, producendo il circuito complessivamente meno profondo (leggermente migliore di `pm_3`) e raggiungendo `pm_3` la stessa fedeltà entro i margini di errore. Lo fa con lo stesso tempo di transpilazione dell'impostazione predefinita, poiché la riscrittura è sostanzialmente gratuita rispetto alla ricerca stocastica di SABRE.\n",
        "\n",
        "Si noti che un aumento `max_iterations` di non ha sempre un impatto positivo. In questo caso è stato di grande aiuto, ma per altri circuiti o backend le iterazioni aggiuntive potrebbero non portare a ulteriori miglioramenti, o potrebbero addirittura compromettere leggermente le prestazioni a causa di un’eccessiva ottimizzazione di un minimo locale. In generale, è consigliabile aumentare `layout_trials` e `swap_trials` il più possibile, nei limiti del tempo a disposizione, poiché un numero maggiore di tentativi aumenta sempre la probabilità di trovare una disposizione migliore. L'aumento `max_iterations` merita di essere testato, ma dovrebbe essere verificato in base al vostro caso d'uso specifico. I passaggi specializzati come `StarPreRouting` sono simili nello spirito, ma dipendono maggiormente dal circuito: sono utili solo quando il circuito contiene effettivamente la struttura a cui si riferiscono. Il guadagno è elevato quando applicabile e pari a zero in caso contrario, ma provarle non costa praticamente nulla.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c2b60e42-4aa2-4dc1-8a11-022072e79776",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Esempio di hardware su larga scala\n",
        "\n",
        "Oltre a regolare il numero di prove, SABRE consente di personalizzare **l** 'euristica di instradamento. SABRE offre tre euristiche:\n",
        "\n",
        "* **`basic`**: Un semplice approccio avido che seleziona lo scambio in grado di ridurre al minimo la distanza immediata dal cancello successivo.\n",
        "* **`decay`** (impostazione predefinita): assegna dinamicamente un peso ai qubit in base all'attività recente, scoraggiando lo scambio ripetuto degli stessi qubit.\n",
        "* **`lookahead`**: Valuta i costi di instradamento futuri analizzando in anticipo i gate successivi, individuando eventualmente sequenze di scambio più vantaggiose.\n",
        "\n",
        "Per utilizzare un'euristica personalizzata, creare un `SabreSwap` pass e collegarlo a `SabreLayout` tramite il `routing_pass` parametro.\n",
        "\n",
        "`SabreSwap` Al confronto viene aggiunto un quarto gestore di passaggi: `pm_star_hw`, che mantiene le impostazioni predefinite `SabreLayout` ma aggiunge `StarPreRouting` alla fase di inizializzazione. A questa scala (100 qubit) la ricerca SABRE risulta più complessa, e la riscrittura da una struttura a stella a una catena lineare si rivela chiaramente vantaggiosa, poiché un processore Heron dispone di percorsi lineari sufficientemente lunghi da ospitare il circuito risultante.\n",
        "\n",
        "Qui mettiamo a confronto tutte e tre le euristiche SABRE, oltre `StarPreRouting` a una simulazione su larga scala su un circuito GHZ da 100 qubit. Eseguiamo diverse prove di layout con semi diversi per le configurazioni SABRE, selezioniamo il circuito transpilato migliore da ciascuna di esse e li sottoponiamo tutti a test su hardware reale insieme al `StarPreRouting` risultato.\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",
        "### I passaggi da 1 a 4 raggruppati in un unico blocco di codice\n",
        "\n",
        "Qui il flusso di lavoro completo viene messo insieme su scala più ampia. Quando si utilizza `SabreSwap` come per `routing_pass` `SabreLayout`, viene eseguita una sola prova di layout per ogni chiamata; pertanto, la seguente cella di codice esegue un ciclo sui valori di seed per esplorare lo spazio dei layout.\n",
        "\n",
        "Utilizziamo lo stesso `wrap_sabre` helper definito nella Fase 2 su piccola scala (sopra) e aggiungiamo un helper analogo `wrap_routing` , poiché anche lo `routing` stage all'indice \\[1] è un `ConditionalController([BarrierBeforeFinalMeasurements, routing_pass], ...)` — sostituirlo semplicemente eliminerebbe, analogamente, la barriera protettiva e il `_swap_condition` gating.\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",
        "### Analisi\n",
        "\n",
        "I grafici a dispersione mostrano una notevole variabilità tra i vari semi per tutte e tre le euristiche SABRE, il che sottolinea l’importanza di eseguire più prove di layout piuttosto che affidarsi a un’unica transpilazione. La `StarPreRouting` linea rimane sostanzialmente piatta per tutti i seed, poiché la riscrittura da una struttura a stella a una catena lineare è deterministica, data la struttura; l’instradamento SABRE a valle ha quindi pochissima libertà su una catena lineare, per cui il seed non ha quasi alcun effetto sulla profondità o sulla dimensione finale.\n",
        "\n",
        "Dai risultati della transpilazione emerge che entrambe le `decay` euristiche e `lookahead` superano `basic` costantemente con un ampio margine. L'euristica `basic` , pur essendo veloce, utilizza una semplice strategia avida che spesso porta alla creazione di circuiti notevolmente più profondi. Per questo circuito GHZ a topologia a stella, `lookahead` tende a produrre la profondità di ricerca \" 2Q \" e il numero di gate più bassi tra le euristiche SABRE, poiché la sua funzione di costo orientata al futuro è particolarmente adatta ai circuiti con modelli di connettività a lungo raggio. `StarPreRouting`, tuttavia, supera di gran lunga tutte e tre le soluzioni: riscrivendo la stella in una catena lineare prima dell'instradamento, aggira completamente il problema della ricerca e fornisce un circuito che il resto del transpiler può mappare su un percorso lineare con un numero minimo di SWAP aggiuntivi.\n",
        "\n",
        "Questo vantaggio si riflette direttamente sulla fedeltà dell'hardware. Una minore profondità dell’ 2Q e e un numero inferiore di gate non si traducono sempre, in modo diretto, in una maggiore fedeltà (anche i qubit fisici specifici utilizzati da un layout e la loro calibrazione in fase di esecuzione hanno la loro importanza), ma quando il divario di profondità è così ampio come quello tra SABRE e `StarPreRouting` qui, l’approccio “structure-aware” prevale in modo decisivo, poiché il circuito accumula una decoerenza di gran lunga inferiore e un numero di eventi di errore a due qubit molto più ridotto. Il grafico a barre della fedeltà mostra `StarPreRouting` che è nettamente superiore persino alla migliore euristica SABRE, mentre `basic` si colloca ben al di sotto degli altri perché i suoi circuiti, molto più complessi, accumulano il maggior numero di errori.\n",
        "\n",
        "**Punti chiave:**\n",
        "\n",
        "* Tra gli algoritmi euristici SABRE, `decay` e `lookahead` risultano sostanzialmente migliori di `basic` per i circuiti non banali. Per i carichi di lavoro di produzione, è preferibile utilizzare una delle due opzioni.\n",
        "* L'euristica SABRE più adatta dipende dal circuito e dall'hardware utilizzati. Testare più euristiche con più semi è la strategia più affidabile.\n",
        "* Se vuoi esplorare un numero ancora maggiore di layout, aumenta `swap_trials` (e `layout_trials` quando non stai fissando un passaggio di instradamento personalizzato) anziché distribuire il lavoro su nodi remoti. I passaggi SABRE parallelizzano già le prove tra i thread locali, e il carico di lavoro per singola prova è talmente ridotto che il sovraccarico di distribuzione finisce in genere per prevalere su qualsiasi aumento di velocità.\n",
        "* Quando il circuito presenta una struttura speciale nota, l'applicazione di un passaggio che tenga conto della struttura, come `StarPreRouting` quello precedente a SABRE, può garantire un miglioramento di un ordine di grandezza che nessuna ottimizzazione di SABRE potrà eguagliare. Questo non sostituisce SABRE: `StarPreRouting` è utile solo quando il circuito contiene effettivamente sottocircuiti a stella e il backend presenta un percorso lineare sufficientemente lungo. Quando conosci la forma del tuo circuito, vale la pena controllare se ci sono corrispondenze nella libreria dei pass.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65c2cebe-50b9-4304-9123-bf4cea7ecff6",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti contenuti:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  * [`SabreLayout` Riferimento API](/docs/api/qiskit/qiskit.transpiler.passes.SabreLayout) : documentazione completa sui parametri\n",
        "  * [Documento SABRE](https://arxiv.org/abs/1809.02573) : l'algoritmo SABRE originale per il layout e il routing\n",
        "  * [LightSABRE articolo](https://arxiv.org/abs/2409.08368) : i miglioramenti algoritmici alla base dell'attuale implementazione di SABRE in Qiskit\n",
        "  * [Scrivi un passaggio di transpiler personalizzato](/docs/guides/custom-transpiler-pass) : crea la tua logica di transpilazione\n",
        "  * [Plugin di transpiler](/docs/guides/transpiler-plugins) : estendere la pipeline di transpilazione di Qiskit con passaggi di terze parti\n",
        "  * [Rappresentazione DAG](/docs/guides/DAG-representation) : comprendere il grafo aciclico diretto utilizzato internamente dal transpiler\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42d1c053-1683-4c32-be1b-a36602207f74",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## Sondaggio tutorial\n",
        "\n",
        "Vi invitiamo a partecipare a questo breve sondaggio per fornire un feedback su questa esercitazione. Le vostre indicazioni ci aiuteranno a migliorare la nostra offerta di contenuti e l'esperienza degli utenti.\n",
        "\n",
        "[Collegamento al sondaggio](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
}