{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "44d97249",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Entanglement a lungo raggio con circuiti dinamici\"\n",
        "description: \"Questo tutorial implementa un CNOT a lungo raggio utilizzando circuiti dinamici con coppie di Bell, misurazioni e feedforward, e lo confronta con un approccio unitario diretto.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore fontsize hcords, ycords, xcords, ecolor, elinewidth, allcr, braket, frameon */}\n",
        "\n",
        "<span id=\"long-range-entanglement-with-dynamic-circuits\" />\n",
        "\n",
        "# Entanglement a lungo raggio con circuiti dinamici\n",
        "\n",
        "*Stima di utilizzo: 4 minuti su un processore Heron r2. (NOTA: questa è solo una stima. Il tempo di esecuzione può variare)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "57f65bca",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "Al termine di questo tutorial, avrai imparato quanto segue:\n",
        "\n",
        "* Come implementare una porta CNOT a lungo raggio utilizzando circuiti dinamici con misurazioni a metà circuito (MCM) e feedforward classico;\n",
        "* Come implementare il gate equivalente utilizzando un approccio unitario basato su SWAP;\n",
        "* Come confrontare i due approcci misurando la fedeltà del gate in funzione della distanza tra i qubit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "748eee3b",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prerequisiti\n",
        "\n",
        "Consigliamo agli utenti di acquisire familiarità con i seguenti argomenti prima di seguire questo tutorial:\n",
        "\n",
        "* [Concetti fondamentali dell'informatica quantistica](/learning/courses/basics-of-quantum-information), tra cui gli stati di Bell, l'entanglement e le porte quantistiche;\n",
        "* Conoscenza dei [circuiti dinamici](/docs/guides/classical-feedforward-and-control-flow) (misurazioni a metà circuito e feedforward classico);\n",
        "* Conoscenze di base di [Qiskit SDK](/docs/guides) e [del servizio IBM Quantum® Compute](/docs/guides/compute-services#qiskit-runtime), nonché accesso a un [account IBM Quantum](/docs/guides/cloud-setup).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b05b669f",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "L'entanglement a lungo raggio tra qubit distanti è una sfida per i dispositivi con connettività limitata. Questo tutorial mostra come i circuiti dinamici possano generare tale entanglement implementando un gate LRCX (long-range controlled-X) che utilizza un protocollo basato sulla misurazione.\n",
        "\n",
        "Seguendo l'approccio di Elisa Bäumer et al. in [1](#ref-1), il metodo utilizza la misurazione a metà circuito e il feedforward per ottenere porte a profondità costante indipendentemente dalla separazione dei qubit. Crea coppie intermedie di Bell, misura un qubit da ciascuna coppia e applica porte condizionate classiche per propagare l'entanglement attraverso il dispositivo. In questo modo si evitano lunghe catene SWAP, riducendo sia la profondità del circuito che l'esposizione agli errori dei gate a due qubit.\n",
        "\n",
        "In questo articolo adattiamo il protocollo all'hardware dell' IBM Quantum e e ne valutiamo le prestazioni in funzione della distanza tra il sistema di controllo e il bersaglio, confrontandolo con un modello di riferimento unitario basato su SWAP.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c6cd3174",
      "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.37 o versioni successive (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer v0.17 o versioni successive (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "39e4440a",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "5b0aa550",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister\n",
        "from qiskit.circuit.classical import expr\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit.visualization import plot_circuit_layout\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    Batch,\n",
        "    SamplerV2 as Sampler,\n",
        ")\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6733ceeb",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Esempio di simulatore su piccola scala\n",
        "\n",
        "Prima di eseguire il codice sulla QPU reale, verifichiamo che sia il circuito dinamico che quello unitario producano uno stato di Bell ideale su un simulatore privo di rumore. Utilizziamo l' IBM Quantum `Sampler` con come `AerSimulator` modalità di backend, a una distanza minima di 6.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28145093",
      "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",
        "Ora implementiamo una porta CNOT a lungo raggio tra due qubit distanti, seguendo la costruzione del circuito dinamico illustrata di seguito (adattata dalla Fig. 1a nel rif. [1](#ref-1) ). L'idea fondamentale consiste nell'utilizzare un \"bus\" di qubit ausiliari, inizializzati a $|0\\rangle$, per mediare il teletrasporto tramite gate a lungo raggio.\n",
        "\n",
        "![Circuito CNOT a lungo raggio](https://quantum.cloud.ibm.com/docs/images/tutorials/long-range-entanglement/dynamic_vs_unitary_long_range_illustration.avif)\n",
        "\n",
        "Come illustrato nella figura, il processo funziona come segue:\n",
        "\n",
        "1. Preparare una catena di coppie di Bell che collegano i qubit di controllo e di destinazione tramite ancillas intermedie.\n",
        "2. Eseguire misure di Bell tra qubit vicini non entangled, scambiando l'entanglement passo dopo passo finché il controllo e il bersaglio non condividono una coppia di Bell.\n",
        "3. Utilizzare questa coppia di Bell per il teletrasporto di porte, trasformando un CNOT locale in un CNOT deterministico a lungo raggio a profondità costante.\n",
        "\n",
        "Questo approccio sostituisce le lunghe catene SWAP con un protocollo a profondità costante, riducendo l'esposizione agli errori dei gate a due qubit e rendendo l'operazione scalabile con le dimensioni del dispositivo.\n",
        "\n",
        "Di seguito, illustreremo innanzitutto l'implementazione dinamica del circuito LRCX. Alla fine, forniremo anche un'implementazione unitaria di confronto, per evidenziare i vantaggi dei circuiti dinamici in questo contesto.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89597fcf",
      "metadata": {},
      "source": [
        "<span id=\"initialize-circuit\" />\n",
        "\n",
        "#### Inizializzare il circuito\n",
        "\n",
        "Iniziamo con un semplice problema quantistico che fungerà da termine di paragone. Nello specifico, inizializziamo un circuito con un qubit di controllo all'indice 0 e vi applichiamo un gate di Hadamard. Ciò produce uno stato di sovrapposizione che, se seguito da un'operazione X controllata, genera uno stato di Bell $(|00\\rangle + |11\\rangle)/\\sqrt{2}$ tra i qubit di controllo e quelli bersaglio.\n",
        "\n",
        "In questa fase, non stiamo ancora costruendo il controllo a lungo raggio (LRCX). Il nostro obiettivo è invece quello di definire un circuito iniziale chiaro e minimale che evidenzi il ruolo della LRCX. Nella fase 2, mostreremo come l'LRCX possa essere implementato come ottimizzazione utilizzando circuiti dinamici e confronteremo le sue prestazioni con quelle di un equivalente unitario. È importante notare che il protocollo LRCX può essere applicato a qualsiasi circuito iniziale. Per chiarezza dimostrativa utilizziamo questa semplice configurazione di Hadamard.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0446b8e8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/0446b8e8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 20,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "distance = 6  # The distance of the CNOT gate, with the convention that a distance of zero is a nearest-neighbor CNOT.\n",
        "\n",
        "\n",
        "def initialize_circuit(distance):\n",
        "    assert distance >= 0\n",
        "    control = 0  # control qubit\n",
        "    n = distance  # number of qubits between target and control\n",
        "\n",
        "    qr = QuantumRegister(\n",
        "        n + 2, name=\"q\"\n",
        "    )  # Circuit with n qubits between control and target\n",
        "    cr = ClassicalRegister(\n",
        "        2, name=\"cr\"\n",
        "    )  # Classical register for measuring control and target qubits\n",
        "\n",
        "    k = int(n / 2)  # Number of Bell States to be used\n",
        "\n",
        "    allcr = [cr]\n",
        "    if (\n",
        "        distance > 1\n",
        "    ):  # This classical register will be used to store ZZ measurements.\n",
        "        # It is only used for long-range CX gates with distance > 1\n",
        "        c1 = ClassicalRegister(\n",
        "            k, name=\"c1\"\n",
        "        )  # Classical register needed for post processing\n",
        "        allcr.append(c1)\n",
        "    if (\n",
        "        distance > 0\n",
        "    ):  # This classical register will be used to store XX measurements.\n",
        "        # It is only used if distance > 0\n",
        "        c2 = ClassicalRegister(\n",
        "            n - k, name=\"c2\"\n",
        "        )  # Classical register needed for post processing\n",
        "        allcr.append(c2)\n",
        "\n",
        "    qc = QuantumCircuit(qr, *allcr, name=\"CNOT\")\n",
        "\n",
        "    # Apply a Hadamard gate to the control qubit such that the\n",
        "    # long-range CNOT gate will prepare a\n",
        "    # Bell state (|00> + |11>)/sqrt(2)\n",
        "    qc.h(control)\n",
        "\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = initialize_circuit(distance)\n",
        "qc.draw(fold=-1, output=\"mpl\", scale=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e761cfc1",
      "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",
        "In questa fase, mostriamo come costruire il circuito LRCX utilizzando circuiti dinamici. L'obiettivo è ottimizzare il circuito per l'esecuzione su hardware riducendo la profondità rispetto a un'implementazione puramente unitaria. Per illustrare i vantaggi, mostreremo sia la costruzione LRCX dinamica che il suo equivalente unitario, per poi confrontare le loro prestazioni dopo la trasposizione. È importante notare che, mentre qui applichiamo il LRCX a un semplice problema inizializzato con Hadamard, il protocollo può essere applicato a qualsiasi circuito in cui sia richiesto un CNOT a lungo raggio.\n",
        "\n",
        "<span id=\"prepare-bell-pairs\" />\n",
        "\n",
        "#### Preparare le coppie di campane\n",
        "\n",
        "Si inizia creando una catena di coppie di Bell lungo il percorso tra i qubit di controllo e di destinazione. Se la distanza è dispari, si applica prima un CNOT dal controllo al suo vicino, che è il CNOT che verrà teletrasportato. Per una distanza uniforme, questo CNOT verrà applicato dopo la fase di preparazione della coppia di campane. La catena di coppie di Bell aggancia quindi coppie successive di qubit, stabilendo la risorsa necessaria per trasportare le informazioni di controllo attraverso il dispositivo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "4df8ebba",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/4df8ebba-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Determine where to start the Bell pair chain and add an extra CNOT when n is odd\n",
        "def check_even(n: int) -> int:\n",
        "    \"\"\"Return 1 if n is even, else 2.\"\"\"\n",
        "    return 1 if n % 2 == 0 else 2\n",
        "\n",
        "\n",
        "def prepare_bell_pairs(qc, add_barriers=True):\n",
        "    n = qc.num_qubits - 2  # number of qubits between target and control\n",
        "    k = int(n / 2)\n",
        "\n",
        "    if add_barriers:\n",
        "        qc.barrier()\n",
        "\n",
        "    x0 = check_even(n)\n",
        "    if n % 2 != 0:\n",
        "        qc.cx(0, 1)\n",
        "\n",
        "    # Create k Bell pairs\n",
        "    for i in range(k):\n",
        "        qc.h(x0 + 2 * i)\n",
        "        qc.cx(x0 + 2 * i, x0 + 2 * i + 1)\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = prepare_bell_pairs(qc)\n",
        "qc.draw(output=\"mpl\", fold=-1, scale=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b80ea657",
      "metadata": {},
      "source": [
        "<span id=\"measure-neighboring-qubit-pairs-in-the-bell-basis\" />\n",
        "\n",
        "#### Misurare coppie di qubit adiacenti nella base di Bell\n",
        "\n",
        "Successivamente, misuriamo i qubit vicini *non entangled* nella base di Bell (misure a due qubit di $XX$ e $ZZ$ ). In questo modo si crea una coppia di Bell a lungo raggio tra il qubit bersaglio e il qubit adiacente al controllo (fino alle correzioni di Pauli, che saranno implementate tramite feedforward nella fase successiva). In parallelo, implementiamo la misura di entangling che teletrasporta il gate CNOT ad agire sul qubit target previsto.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8eed9e57",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/8eed9e57-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "def measure_bell_basis(qc, add_barriers=True):\n",
        "    n = qc.num_qubits - 2  # number of qubits between target and control\n",
        "    k = int(n / 2)\n",
        "\n",
        "    if n > 1:\n",
        "        _, c1, c2 = qc.cregs\n",
        "    elif n > 0:\n",
        "        _, c2 = qc.cregs\n",
        "\n",
        "    # Determine where to start the Bell pair chain and add an extra CNOT\n",
        "    # when n is odd\n",
        "    x0 = 1 if n % 2 == 0 else 2\n",
        "\n",
        "    # Entangling layer that implements the Bell measurement\n",
        "    # (and additionally adds the CNOT to be\n",
        "    # teleported, if n is even)\n",
        "    for i in range(k + 1):\n",
        "        qc.cx(x0 - 1 + 2 * i, x0 + 2 * i)\n",
        "\n",
        "    for i in range(1, k + x0):\n",
        "        if i == 1:\n",
        "            qc.h(2 * i + 1 - x0)\n",
        "        else:\n",
        "            qc.h(2 * i + 1 - x0)\n",
        "\n",
        "    if add_barriers:\n",
        "        qc.barrier()\n",
        "\n",
        "    # Map the ZZ measurements onto classical register c1\n",
        "    for i in range(k):\n",
        "        if i == 0:\n",
        "            qc.measure(2 * i + x0, c1[i])\n",
        "        else:\n",
        "            qc.measure(2 * i + x0, c1[i])\n",
        "\n",
        "    # Map the XX measurements onto classical register c2\n",
        "    for i in range(1, k + x0):\n",
        "        if i == 1:\n",
        "            qc.measure(2 * i + 1 - x0, c2[i - 1])\n",
        "        else:\n",
        "            qc.measure(2 * i + 1 - x0, c2[i - 1])\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = measure_bell_basis(qc)\n",
        "qc.draw(output=\"mpl\", fold=-1, scale=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f0d7059b",
      "metadata": {},
      "source": [
        "<span id=\"apply-feedforward-corrections-to-correct-pauli-byproduct-operators\" />\n",
        "\n",
        "#### Applicare correzioni feedforward agli operatori dei sottoprodotti di Pauli\n",
        "\n",
        "Le misure in base Bell introducono sottoprodotti di Pauli che devono essere corretti utilizzando i risultati registrati. Ciò avviene in due fasi. In primo luogo, è necessario calcolare la parità di tutte le misure di $ZZ$, che viene poi utilizzata per applicare in modo condizionale un gate $X$ al qubit di destinazione. Allo stesso modo, la parità delle misure di $XX$ viene calcolata e utilizzata per applicare condizionalmente un gate $Z$ al qubit di controllo.\n",
        "\n",
        "Con il nuovo framework di espressioni classiche di Qiskit, queste parità possono essere calcolate direttamente nel livello di elaborazione classica del circuito. Invece di applicare una sequenza di porte condizionali individuali per ogni bit di misura, possiamo costruire una singola espressione classica che rappresenta lo XOR (parità) di tutti i risultati di misura rilevanti. Questa espressione viene quindi utilizzata come condizione in un singolo blocco `if_test` , consentendo di applicare le porte di correzione a profondità costante. Questo approccio semplifica il circuito e garantisce che le correzioni di feedforward non introducano un'inutile latenza aggiuntiva.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "4915791a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/4915791a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "def apply_ffwd_corrections(qc):\n",
        "    control = 0  # control qubit\n",
        "    target = qc.num_qubits - 1  # target qubit\n",
        "    n = qc.num_qubits - 2  # number of qubits between target and control\n",
        "\n",
        "    k = int(n / 2)\n",
        "    x0 = check_even(n)\n",
        "\n",
        "    if n > 1:\n",
        "        _, c1, c2 = qc.cregs\n",
        "    elif n > 0:\n",
        "        _, c2 = qc.cregs\n",
        "\n",
        "    # First, let's compute the parity of all ZZ measurements\n",
        "    for i in range(k):\n",
        "        if i == 0:\n",
        "            parity_ZZ = expr.lift(\n",
        "                c1[i]\n",
        "            )  # Store the value of the first ZZ measurement in parity_ZZ\n",
        "        else:\n",
        "            parity_ZZ = expr.bit_xor(\n",
        "                c1[i], parity_ZZ\n",
        "            )  # Successively compute the parity via XOR operations\n",
        "\n",
        "    for i in range(1, k + x0):\n",
        "        if i == 1:\n",
        "            parity_XX = expr.lift(\n",
        "                c2[i - 1]\n",
        "            )  # Store the value of the first XX measurement in parity_XX\n",
        "        else:\n",
        "            parity_XX = expr.bit_xor(\n",
        "                c2[i - 1], parity_XX\n",
        "            )  # Successively compute the parity via XOR operations\n",
        "\n",
        "    if n > 0:\n",
        "        with qc.if_test(parity_XX):\n",
        "            qc.z(control)\n",
        "\n",
        "    if n > 1:\n",
        "        with qc.if_test(parity_ZZ):\n",
        "            qc.x(target)\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = apply_ffwd_corrections(qc)\n",
        "qc.draw(output=\"mpl\", fold=-1, scale=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6d22740b",
      "metadata": {},
      "source": [
        "<span id=\"measure-control-and-target-qubits\" />\n",
        "\n",
        "#### Controllo delle misure e qubit di riferimento\n",
        "\n",
        "Definiamo una funzione helper che consente di misurare i qubit di controllo e di destinazione nelle basi $XX$, $YY$, o $ZZ$. Per verificare lo stato di Bell $(|00\\rangle + |11\\rangle)/\\sqrt{2}$, i valori di aspettativa di $XX$ e $ZZ$ devono essere entrambi $+1$, poiché sono stabilizzatori dello stato. La misura $YY$ è supportata anche in questo caso e verrà utilizzata di seguito per calcolare la fedeltà.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d087d7c1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/d087d7c1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def measure_in_basis(qc, basis=\"XX\", add_barrier=True):\n",
        "    control = 0  # control qubit\n",
        "    target = qc.num_qubits - 1  # target qubit\n",
        "\n",
        "    assert basis in [\"XX\", \"YY\", \"ZZ\"]\n",
        "\n",
        "    qc = (\n",
        "        qc.copy()\n",
        "    )  # We copy the circuit because we want to measure in different bases\n",
        "    cr = qc.cregs[0]\n",
        "\n",
        "    if add_barrier:\n",
        "        qc.barrier()\n",
        "\n",
        "    if basis == \"XX\":\n",
        "        qc.h(control)\n",
        "        qc.h(target)\n",
        "    elif basis == \"YY\":\n",
        "        qc.sdg(control)\n",
        "        qc.sdg(target)\n",
        "        qc.h(control)\n",
        "        qc.h(target)\n",
        "\n",
        "    qc.measure(control, cr[0])\n",
        "    qc.measure(target, cr[1])\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc_YY = measure_in_basis(qc.copy(), basis=\"YY\")\n",
        "qc_YY.draw(\n",
        "    output=\"mpl\", fold=-1, scale=0.5\n",
        ")  # Circuit for measuring in the YY basis"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "072f8605",
      "metadata": {},
      "source": [
        "<span id=\"put-it-all-together\" />\n",
        "\n",
        "#### Metti tutto insieme\n",
        "\n",
        "Uniamo le varie fasi sopra descritte per creare un gate CX a lungo raggio alle due estremità di una linea unidimensionale ( 1D ). I passaggi sono i seguenti:\n",
        "\n",
        "* Inizializzazione del qubit di controllo in $|+\\rangle$\n",
        "* Preparazione delle coppie di campane\n",
        "* Misurazione di coppie di qubit vicine\n",
        "* Applicazione di correzioni feedforward che dipendono dagli MCM\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "11fc8adc",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/11fc8adc-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def lrcx(distance, prep_barrier=True, pre_measure_barrier=True):\n",
        "    qc = initialize_circuit(distance)\n",
        "    qc = prepare_bell_pairs(qc, prep_barrier)\n",
        "    qc = measure_bell_basis(qc, pre_measure_barrier)\n",
        "    qc = apply_ffwd_corrections(qc)\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = lrcx(distance)\n",
        "# Apply the measurement in the XX, YY, and ZZ bases\n",
        "qc_XX, qc_YY, qc_ZZ = [\n",
        "    measure_in_basis(qc, basis=basis) for basis in [\"XX\", \"YY\", \"ZZ\"]\n",
        "]\n",
        "\n",
        "qc_YY.draw(\n",
        "    output=\"mpl\", fold=-1, scale=0.5\n",
        ")  # Circuit for measuring in the YY basis"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2b1f3f70",
      "metadata": {},
      "source": [
        "<span id=\"unitary-based-implementation-swapping-the-qubits-to-the-middle\" />\n",
        "\n",
        "#### Implementazione unitaria con scambio dei qubit al centro\n",
        "\n",
        "A titolo di confronto, esaminiamo innanzitutto il caso in cui una porta CNOT a lungo raggio venga implementata utilizzando connessioni tra vicini più prossimi e porte unitarie. Nella figura seguente, a sinistra è riportato un circuito per una porta CNOT a lungo raggio che copre una catena di n qubit di tipo \" 1D \", in cui sono presenti solo connessioni tra vicini più prossimi. Al centro vi è una decomposizione unitaria equivalente realizzabile con porte CNOT locali; la profondità del circuito è $O(n)$.\n",
        "\n",
        "![Circuito CNOT a lungo raggio](https://quantum.cloud.ibm.com/docs/images/tutorials/long-range-entanglement/dynamic_vs_unitary_long_range_illustration.avif)\n",
        "\n",
        "Il circuito al centro può essere realizzato come segue:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3f816591",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cnot_unitary(distance):\n",
        "    \"\"\"Generate a long range CNOT gate using local CNOTs on a 1D\n",
        "    chain of qubits subject to n\n",
        "    nearest-neighbor connections only.\n",
        "\n",
        "\n",
        "    Args:\n",
        "        distance (int) : The distance of the CNOT gate,\n",
        "        with the convention that\n",
        "        a distance of 0 is a nearest-neighbor CNOT.\n",
        "\n",
        "    Returns:\n",
        "        QuantumCircuit: A Quantum Circuit implementing a\n",
        "        long-range CNOT gate\n",
        "        between qubit 0 and qubit distance+1\n",
        "    \"\"\"\n",
        "    assert distance >= 0\n",
        "    n = distance  # number of qubits between target and control\n",
        "\n",
        "    qr = QuantumRegister(\n",
        "        n + 2, name=\"q\"\n",
        "    )  # Circuit with n qubits between control and target\n",
        "    cr = ClassicalRegister(\n",
        "        2, name=\"cr\"\n",
        "    )  # Classical register for measuring control and target qubits\n",
        "\n",
        "    qc = QuantumCircuit(qr, cr, name=\"CNOT_unitary\")\n",
        "\n",
        "    control_qubit = 0\n",
        "\n",
        "    qc.h(control_qubit)  # Prepare the control qubit in the |+> state\n",
        "\n",
        "    k = int(n / 2)\n",
        "    qc.barrier()\n",
        "    for i in range(control_qubit, control_qubit + k):\n",
        "        qc.cx(i, i + 1)\n",
        "        qc.cx(i + 1, i)\n",
        "        qc.cx(-i - 1, -i - 2)\n",
        "        qc.cx(-i - 2, -i - 1)\n",
        "    if n % 2 == 1:\n",
        "        qc.cx(k + 2, k + 1)\n",
        "        qc.cx(k + 1, k + 2)\n",
        "    qc.barrier()\n",
        "    qc.cx(k, k + 1)\n",
        "    for i in range(control_qubit, control_qubit + k):\n",
        "        qc.cx(k - i, k - 1 - i)\n",
        "        qc.cx(k - 1 - i, k - i)\n",
        "        qc.cx(k + i + 1, k + i + 2)\n",
        "        qc.cx(k + i + 2, k + i + 1)\n",
        "    if n % 2 == 1:\n",
        "        qc.cx(-2, -1)\n",
        "        qc.cx(-1, -2)\n",
        "\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc_uni = cnot_unitary(distance)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c0c99c20",
      "metadata": {},
      "source": [
        "Ora costruisci i circuiti che effettuano misurazioni nelle basi $XX$, $YY$ e $ZZ$, proprio come abbiamo fatto per i circuiti dinamici sopra descritti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b899e143",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/b899e143-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Apply the measurement in the XX, YY, and ZZ bases\n",
        "qc_uni_XX, qc_uni_YY, qc_uni_ZZ = [\n",
        "    measure_in_basis(qc_uni, basis=basis) for basis in [\"XX\", \"YY\", \"ZZ\"]\n",
        "]\n",
        "\n",
        "qc_uni_YY.draw(\n",
        "    output=\"mpl\", fold=-1, scale=0.5\n",
        ")  # Circuit for measuring in the YY basis"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "81e55064",
      "metadata": {},
      "source": [
        "Ora che abbiamo realizzato sia i circuiti dinamici che quelli unitari per un esempio su piccola scala con `distance=6`, li trasponiamo in modo che possano essere eseguiti inizialmente su un simulatore privo di rumore.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "091e9537",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "aer_backend = AerSimulator()\n",
        "pm_sim = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=aer_backend\n",
        ")\n",
        "\n",
        "# Dynamic circuits\n",
        "isa_sim_dyn = pm_sim.run([qc_XX, qc_YY, qc_ZZ])\n",
        "\n",
        "# Unitary circuits\n",
        "isa_sim_uni = pm_sim.run([qc_uni_XX, qc_uni_YY, qc_uni_ZZ])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fe2fce0e",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Passaggio 3: eseguire utilizzando Qiskit primitives\n",
        "\n",
        "Ora possiamo eseguire l'esperimento sul backend del simulatore senza rumore. Per l'esecuzione dei circuiti utilizziamo il Sampler di Qiskit Runtime con AerSimulator come modalità di backend.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "689c9a46",
      "metadata": {},
      "outputs": [],
      "source": [
        "sampler_sim = Sampler(mode=aer_backend)\n",
        "sim_job = sampler_sim.run(isa_sim_dyn + isa_sim_uni)\n",
        "sim_results = sim_job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5287f612",
      "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",
        "Una volta che gli esperimenti sono stati eseguiti con successo, procediamo alla post-elaborazione dei dati di misurazione per ricavarne metriche significative.\n",
        "In questa fase, procediamo come segue:\n",
        "\n",
        "* Definire metriche di qualità per valutare le prestazioni della CX a lungo termine.\n",
        "* Calcolo dei valori di aspettativa degli operatori di Pauli dai risultati delle misure grezze.\n",
        "* Utilizzare questi dati per calcolare la fedeltà dello stato di Bell generato.\n",
        "\n",
        "In una simulazione silenziosa, verificheremo che la metrica di fedeltà sia $1$ per i circuiti realizzati. Negli esperimenti condotti su QPU reali, questa analisi fornirà un quadro chiaro delle prestazioni dei circuiti dinamici rispetto all'implementazione unitaria di riferimento.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4ba303da",
      "metadata": {},
      "source": [
        "<span id=\"quality-metrics\" />\n",
        "\n",
        "#### Metriche sulla qualità\n",
        "\n",
        "Per valutare l'efficacia del protocollo CX a lungo raggio, misuriamo quanto lo stato di uscita si avvicini allo stato di Bell ideale. Un modo pratico per quantificarlo consiste nel calcolare la fedeltà dello stato utilizzando i valori attesi degli operatori di Pauli. È possibile calcolare la fedeltà di uno stato di Bell rispetto allo stato di controllo e a quello di riferimento una volta note le seguenti quantità: $\\braket{XX}$, $\\braket{YY}$ e $\\braket{ZZ}$. In particolare,\n",
        "\n",
        "$ F = \\frac{1}{4} (1 + \\braket{XX} - \\braket{YY} + \\braket{ZZ})$\n",
        "\n",
        "Per calcolare questi valori di aspettativa dai dati di misura grezzi, definiamo una serie di funzioni di aiuto:\n",
        "\n",
        "* **`compute_ZZ_expectation`**: Dati i conteggi delle misure, calcola il valore di aspettativa di un operatore di Pauli a due qubit nella base $Z$.\n",
        "* **`compute_fidelity`**: Combina i valori di aspettativa di $XX$, $YY$ e $ZZ$ nell'espressione di fedeltà di cui sopra.\n",
        "* **`get_counts_from_bitarray`**: Utilità per estrarre i conteggi dagli oggetti dei risultati del backend.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "5645a9be",
      "metadata": {},
      "outputs": [],
      "source": [
        "def compute_ZZ_expectation(counts):\n",
        "    total = sum(counts.values())\n",
        "    expectation = 0\n",
        "    for bitstring, count in counts.items():\n",
        "        # Ensure bitstring is 2 bits\n",
        "        z1 = (-1) ** (int(bitstring[-1]))\n",
        "        z2 = (-1) ** (int(bitstring[-2]))\n",
        "        expectation += z1 * z2 * count\n",
        "    return expectation / total\n",
        "\n",
        "\n",
        "def compute_fidelity(counts_xx, counts_yy, counts_zz):\n",
        "    xx, yy, zz = [\n",
        "        compute_ZZ_expectation(c) for c in [counts_xx, counts_yy, counts_zz]\n",
        "    ]\n",
        "    return 1 / 4 * (1 + xx - yy + zz)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "ade0c4e6",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Dynamic fidelity (distance=6): 1.0000\n",
            "Unitary fidelity (distance=6): 1.0000\n"
          ]
        }
      ],
      "source": [
        "# Dynamic fidelity\n",
        "counts_xx = sim_results[0].data.cr.get_counts()\n",
        "counts_yy = sim_results[1].data.cr.get_counts()\n",
        "counts_zz = sim_results[2].data.cr.get_counts()\n",
        "fidelity_dyn = compute_fidelity(counts_xx, counts_yy, counts_zz)\n",
        "\n",
        "# Unitary fidelity\n",
        "counts_xx = sim_results[3].data.cr.get_counts()\n",
        "counts_yy = sim_results[4].data.cr.get_counts()\n",
        "counts_zz = sim_results[5].data.cr.get_counts()\n",
        "fidelity_uni = compute_fidelity(counts_xx, counts_yy, counts_zz)\n",
        "\n",
        "print(f\"Dynamic fidelity (distance={distance}): {fidelity_dyn:.4f}\")\n",
        "print(f\"Unitary fidelity (distance={distance}): {fidelity_uni:.4f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaa66ebb",
      "metadata": {},
      "source": [
        "Come ci si aspetterebbe in una simulazione senza rumore, le fedeltà sia nei circuiti dinamici che in quelli unitari sono pari a $1$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9ac23eb2",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Esempio di hardware su larga scala\n",
        "\n",
        "A questo punto riuniamo tutti questi dettagli in un unico flusso di lavoro su scala più ampia, che viene poi eseguito su hardware quantistico reale.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ad4224f2",
      "metadata": {},
      "source": [
        "<span id=\"generate-circuits-for-different-distances\" />\n",
        "\n",
        "### Generare circuiti per diverse distanze\n",
        "\n",
        "Ora siamo in grado di generare circuiti CX a lungo raggio per una serie di distanze tra i qubit che arrivano fino a 60 qubit. Per ogni distanza, costruiamo circuiti che misurano nelle basi $XX$, $YY$ e $ZZ$, che verranno successivamente utilizzati per calcolare le fedeltà.\n",
        "\n",
        "L'elenco delle distanze comprende separazioni a corto e lungo raggio, con `distance = 0` che corrisponde a una CX di prossimità. Queste stesse distanze saranno utilizzate anche per generare i corrispondenti circuiti unitari in un secondo momento, a scopo di confronto.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "72c70b11",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "distances = [\n",
        "    0,\n",
        "    1,\n",
        "    2,\n",
        "    3,\n",
        "    6,\n",
        "    11,\n",
        "    16,\n",
        "    21,\n",
        "    28,\n",
        "    35,\n",
        "    44,\n",
        "    55,\n",
        "    60,\n",
        "]  # Distances for long range CX. distance of 0 is a nearest-neighbor CX\n",
        "distances.sort()\n",
        "assert min(distances) >= 0\n",
        "basis_list = [\"XX\", \"YY\", \"ZZ\"]\n",
        "\n",
        "# Dynamic circuits\n",
        "circuits_dyn = []\n",
        "for distance in distances:\n",
        "    for basis in basis_list:\n",
        "        circuits_dyn.append(\n",
        "            measure_in_basis(lrcx(distance, prep_barrier=False), basis=basis)\n",
        "        )\n",
        "print(f\"Number of circuits: {len(circuits_dyn)}\")\n",
        "\n",
        "# Unitary circuits\n",
        "circuits_uni = []\n",
        "for distance in distances:\n",
        "    for basis in basis_list:\n",
        "        circuits_uni.append(\n",
        "            measure_in_basis(cnot_unitary(distance), basis=basis)\n",
        "        )\n",
        "\n",
        "print(f\"Number of circuits: {len(circuits_uni)}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "698132a5",
      "metadata": {},
      "source": [
        "Ora che disponiamo di circuiti dinamici e unitari per una serie di distanze, siamo pronti per la trasposizione. Per prima cosa è necessario selezionare un dispositivo backend.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "e0476bef",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 2-------------------------\n",
        "# Set up access to IBM Quantum devices\n",
        "from qiskit.circuit import IfElseOp\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=156\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dad36db5",
      "metadata": {},
      "source": [
        "Il passo successivo assicura che il backend supporti l'istruzione `if_else` , necessaria per la nuova versione dei circuiti dinamici. Poiché questa funzione è ancora in fase di accesso anticipato, aggiungiamo esplicitamente `IfElseOp` al target del backend, se non è già disponibile.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "66805cb4",
      "metadata": {},
      "outputs": [],
      "source": [
        "if \"if_else\" not in backend.target.operation_names:\n",
        "    backend.target.add_instruction(IfElseOp, name=\"if_else\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f15cf5e8",
      "metadata": {},
      "source": [
        "<span id=\"use-layer-fidelity-string-for-selecting-1d-chain\" />\n",
        "\n",
        "#### Utilizzare la stringa Layer Fidelity per selezionare una catena d' 1D\n",
        "\n",
        "Poiché vogliamo confrontare le prestazioni dei circuiti dinamici e unitari su una catena 1D, utilizziamo la stringa Layer Fidelity per selezionare una topologia lineare della migliore catena di qubit del dispositivo. In questo modo si garantisce che entrambi i tipi di circuiti siano trasportati con gli stessi vincoli di connettività, consentendo un confronto equo delle loro prestazioni.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "258e3fa1",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[11, 12, 13, 14, 15, 19, 35, 34, 33, 39, 53, 54, 55, 59, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 76, 81, 82, 83, 84, 85, 86, 87, 97, 107, 108, 109, 110, 111, 98, 91, 92, 93, 94, 95, 99, 115, 114, 113, 119, 133, 132, 131, 138, 151, 150, 149, 148]\n"
          ]
        }
      ],
      "source": [
        "# This selects best qubits for longest distance and uses\n",
        "# the same control for all lengths\n",
        "lf_qubits = backend.properties().to_dict()[\n",
        "    \"general_qlists\"\n",
        "]  # best linear chain qubits\n",
        "chosen_layouts = {\n",
        "    distance: [\n",
        "        val[\"qubits\"]\n",
        "        for val in lf_qubits\n",
        "        if val[\"name\"] == f\"lf_{distances[-1] + 2}\"\n",
        "    ][0][: distance + 2]\n",
        "    for distance in distances\n",
        "}\n",
        "print(chosen_layouts[max(distances)])  # best qubits at each distance"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5886f5c9",
      "metadata": {},
      "outputs": [],
      "source": [
        "isa_circuits_dyn = []\n",
        "isa_circuits_uni = []\n",
        "\n",
        "# Using the same initial layouts for both circuits for better\n",
        "# apples to apples comparison\n",
        "for qc in circuits_dyn:\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=1,\n",
        "        backend=backend,\n",
        "        initial_layout=chosen_layouts[qc.num_qubits - 2],\n",
        "    )\n",
        "    isa_circuits_dyn.append(pm.run(qc))\n",
        "\n",
        "for qc in circuits_uni:\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=1,\n",
        "        backend=backend,\n",
        "        initial_layout=chosen_layouts[qc.num_qubits - 2],\n",
        "    )\n",
        "    isa_circuits_uni.append(pm.run(qc))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c77c3fd3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "2Q depth: 2\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/c77c3fd3-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 28,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "print(\n",
        "    f\"2Q depth: \"\n",
        "    f\"{isa_circuits_dyn[14].depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "isa_circuits_dyn[14].draw(\"mpl\", fold=-1, idle_wires=0)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7e5fc240",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "2Q depth: 13\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/7e5fc240-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 29,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "print(\n",
        "    f\"2Q depth: \"\n",
        "    f\"{isa_circuits_uni[14].depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "isa_circuits_uni[14].draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b6995ce7",
      "metadata": {},
      "source": [
        "<span id=\"visualize-qubits-used-for-the-lrcx-circuit\" />\n",
        "\n",
        "### Visualizza i qubit utilizzati per il circuito LRCX\n",
        "\n",
        "In questa sezione esaminiamo come il circuito LRCX viene mappato sull'hardware. Iniziamo con la visualizzazione dei qubit fisici utilizzati nel circuito e poi studiamo come la distanza controllo-target nel layout influisce sul numero di operazioni.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "2d090f8a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/2d090f8a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 30,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Note: the qubit coordinates must be hard-coded.\n",
        "# The backend API does not currently provide this information directly.\n",
        "# If using a different backend, you will need to\n",
        "# adjust the coordinates accordingly,\n",
        "# or set the qubit_coordinates = None to use the default layout coordinates.\n",
        "\n",
        "\n",
        "def _heron_coords_r2():\n",
        "    \"\"\"Generate coordinates for the Heron layout in R2. Note\"\"\"\n",
        "    cord_map = np.array(\n",
        "        [\n",
        "            [\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                3,\n",
        "                7,\n",
        "                11,\n",
        "                15,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                1,\n",
        "                5,\n",
        "                9,\n",
        "                13,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                3,\n",
        "                7,\n",
        "                11,\n",
        "                15,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                1,\n",
        "                5,\n",
        "                9,\n",
        "                13,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                3,\n",
        "                7,\n",
        "                11,\n",
        "                15,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                1,\n",
        "                5,\n",
        "                9,\n",
        "                13,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                3,\n",
        "                7,\n",
        "                11,\n",
        "                15,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "            ],\n",
        "            -1\n",
        "            * np.array([j for i in range(15) for j in [i] * [16, 4][i % 2]]),\n",
        "        ],\n",
        "        dtype=int,\n",
        "    )\n",
        "\n",
        "    hcords = []\n",
        "    ycords = cord_map[0]\n",
        "    xcords = cord_map[1]\n",
        "    for i in range(156):\n",
        "        hcords.append([xcords[i] + 1, np.abs(ycords[i]) + 1])\n",
        "\n",
        "    return hcords\n",
        "\n",
        "\n",
        "# Visualize the active qubits in the circuit layout\n",
        "plot_circuit_layout(\n",
        "    circuit=isa_circuits_uni[-1],\n",
        "    backend=backend,\n",
        "    view=\"physical\",\n",
        "    qubit_coordinates=_heron_coords_r2(),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d75038a",
      "metadata": {},
      "source": [
        "Successivamente, eseguiamo l'esperimento sul backend reale. Utilizziamo inoltre il batching per eseguire l'esperimento in modo efficiente su più prove. L'esecuzione di prove ripetute ci consente di calcolare le medie per un confronto più accurato tra il metodo unitario e quello dinamico, nonché di quantificarne la variabilità confrontando le deviazioni tra le diverse esecuzioni.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "e6f7811d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 3-------------------------\n",
        "num_trials = 10\n",
        "jobs_uni = []\n",
        "jobs_dyn = []\n",
        "with Batch(backend=backend) as batch:\n",
        "    sampler = Sampler(mode=batch)\n",
        "    sampler.options.environment.job_tags = [\"TUT_LRE\"]\n",
        "    for _ in range(num_trials):\n",
        "        jobs_uni.append(sampler.run(isa_circuits_uni, shots=1024))\n",
        "        jobs_dyn.append(sampler.run(isa_circuits_dyn, shots=1024))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "388b6a87",
      "metadata": {},
      "source": [
        "Calcoliamo la fedeltà dei circuiti CX dinamici a lungo raggio. Per ciascuna distanza, estraiamo i risultati delle misurazioni nei database $\\braket{XX}$, $\\braket{YY}$ e $\\braket{ZZ}$. Questi risultati vengono combinati utilizzando le funzioni di supporto definite in precedenza per calcolare la fedeltà secondo $F = \\tfrac{1}{4} \\big( 1 + \\langle XX \\rangle - \\langle YY \\rangle + \\langle ZZ \\rangle \\big)$. Ciò fornisce la fedeltà osservata del protocollo eseguito dinamicamente a ciascuna distanza.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "10245513",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 4-------------------------\n",
        "fidelities_dyn = []\n",
        "\n",
        "# loop over trials\n",
        "for job in jobs_dyn:\n",
        "    result_dyn = job.result()\n",
        "    trial_fidelities = []\n",
        "    # loop over all distances\n",
        "    for ind, dist in enumerate(distances):\n",
        "        counts_xx = result_dyn[ind * 3].data.cr.get_counts()\n",
        "        counts_yy = result_dyn[ind * 3 + 1].data.cr.get_counts()\n",
        "        counts_zz = result_dyn[ind * 3 + 2].data.cr.get_counts()\n",
        "        trial_fidelities.append(\n",
        "            compute_fidelity(counts_xx, counts_yy, counts_zz)\n",
        "        )\n",
        "    fidelities_dyn.append(trial_fidelities)\n",
        "# average over trials for each distance\n",
        "avg_fidelities_dyn = np.mean(fidelities_dyn, axis=0)\n",
        "std_fidelities_dyn = np.std(fidelities_dyn, axis=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dabcde16",
      "metadata": {},
      "source": [
        "Ora calcoliamo la fedeltà per i circuiti CX unitari a lungo raggio e lo facciamo nello stesso modo in cui abbiamo fatto per i circuiti dinamici di cui sopra.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "decaf83a",
      "metadata": {},
      "outputs": [],
      "source": [
        "fidelities_uni = []\n",
        "\n",
        "# loop over trials\n",
        "for job in jobs_uni:\n",
        "    result_uni = job.result()\n",
        "    trial_fidelities = []\n",
        "    # loop over all distances\n",
        "    for ind, dist in enumerate(distances):\n",
        "        counts_xx = result_uni[ind * 3].data.cr.get_counts()\n",
        "        counts_yy = result_uni[ind * 3 + 1].data.cr.get_counts()\n",
        "        counts_zz = result_uni[ind * 3 + 2].data.cr.get_counts()\n",
        "        trial_fidelities.append(\n",
        "            compute_fidelity(counts_xx, counts_yy, counts_zz)\n",
        "        )\n",
        "    fidelities_uni.append(trial_fidelities)\n",
        "# average over trials for each distance\n",
        "avg_fidelities_uni = np.mean(fidelities_uni, axis=0)\n",
        "std_fidelities_uni = np.std(fidelities_uni, axis=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a9dadc4e",
      "metadata": {},
      "source": [
        "<span id=\"plot-the-results\" />\n",
        "\n",
        "### Traccia i risultati\n",
        "\n",
        "Per comprendere meglio i risultati dal punto di vista visivo, il grafico sottostante illustra le fedeltà di gate stimate, misurate a diverse distanze tra i qubit intrecciati per ciascun metodo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "724da22d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/724da22d-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, ax = plt.subplots()\n",
        "\n",
        "# Unitary with error bars\n",
        "ax.errorbar(\n",
        "    distances,\n",
        "    avg_fidelities_uni,\n",
        "    yerr=std_fidelities_uni,\n",
        "    fmt=\"o-.\",\n",
        "    color=\"c\",\n",
        "    ecolor=\"c\",\n",
        "    elinewidth=1,\n",
        "    capsize=4,\n",
        "    label=\"Unitary\",\n",
        ")\n",
        "# Dynamic with error bars\n",
        "ax.errorbar(\n",
        "    distances,\n",
        "    avg_fidelities_dyn,\n",
        "    yerr=std_fidelities_dyn,\n",
        "    fmt=\"o-.\",\n",
        "    color=\"m\",\n",
        "    ecolor=\"m\",\n",
        "    elinewidth=1,\n",
        "    capsize=4,\n",
        "    label=\"Dynamic\",\n",
        ")\n",
        "# Random gate baseline\n",
        "ax.axhline(y=1 / 4, linestyle=\"--\", color=\"gray\", label=\"Random gate\")\n",
        "\n",
        "legend = ax.legend(frameon=True)\n",
        "for text in legend.get_texts():\n",
        "    text.set_color(\"black\")\n",
        "legend.get_frame().set_facecolor(\"white\")\n",
        "legend.get_frame().set_edgecolor(\"black\")\n",
        "ax.set_title(\n",
        "    \"Bell State Fidelity vs Control–Target Separation\", color=\"black\"\n",
        ")\n",
        "ax.set_xlabel(\"Distance\", color=\"black\")\n",
        "ax.set_ylabel(\"Bell state fidelity\", color=\"black\")\n",
        "ax.grid(linestyle=\":\", linewidth=0.6, alpha=0.4, color=\"gray\")\n",
        "ax.set_ylim((0.2, 1))\n",
        "ax.set_facecolor(\"white\")\n",
        "fig.patch.set_facecolor(\"white\")\n",
        "for spine in ax.spines.values():\n",
        "    spine.set_visible(True)\n",
        "    spine.set_color(\"black\")\n",
        "ax.tick_params(axis=\"x\", colors=\"black\")\n",
        "ax.tick_params(axis=\"y\", colors=\"black\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb2545e7",
      "metadata": {},
      "source": [
        "Dal grafico di fedeltà sopra riportato, l'LRCX non ha superato in modo consistente l'implementazione unitaria diretta. In effetti, per brevi separazioni controllo-bersaglio, il circuito unitario ha ottenuto una maggiore fedeltà. Tuttavia, a separazioni maggiori, il circuito dinamico inizia a ottenere una migliore fedeltà rispetto all'implementazione unitaria. Questo comportamento non è inaspettato sull'hardware attuale: se da un lato i circuiti dinamici riducono la profondità del circuito evitando lunghe catene di SWAP, dall'altro introducono un tempo di circuito aggiuntivo dovuto alle misure a metà circuito, al feedforward classico e ai ritardi del percorso di controllo. La latenza aggiunta aumenta la decoerenza e gli errori di lettura, che possono superare il risparmio di profondità a brevi distanze.\n",
        "\n",
        "Tuttavia, osserviamo un punto di incrocio in cui l'approccio dinamico supera quello unitario. Questo è un risultato diretto della diversa scala: la profondità del circuito unitario cresce linearmente con la distanza tra i qubit, mentre la profondità del circuito dinamico rimane costante.\n",
        "\n",
        "**Punti chiave:**\n",
        "\n",
        "* **Vantaggi immediati dei circuiti dinamici:** La principale motivazione attuale è la riduzione della *profondità dei due qubit*, non necessariamente una maggiore fedeltà.\n",
        "* **Perché la fedeltà può essere peggiore oggi:** L'aumento del tempo di circuito dovuto alle operazioni di misura e classiche spesso domina, soprattutto quando la separazione controllo-obiettivo è piccola.\n",
        "* **In prospettiva:** Con il miglioramento dell'hardware, in particolare con una lettura più veloce, una latenza di controllo classica più breve e una riduzione dell'overhead del circuito centrale, dovremmo aspettarci che queste riduzioni di profondità e durata si traducano in guadagni di fedeltà misurabili.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3dcff343",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/3dcff343-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Compute metrics for each distance, skipping the basis circuits since\n",
        "# they are identical for each distance\n",
        "depths_2q_dyn = [\n",
        "    c.depth(lambda x: x.operation.num_qubits == 2)\n",
        "    for c in isa_circuits_dyn[::3]\n",
        "]\n",
        "meas_dyn = [\n",
        "    sum(1 for instr in c.data if instr.operation.name == \"measure\")\n",
        "    for c in isa_circuits_dyn[::3]\n",
        "]\n",
        "\n",
        "depths_2q_uni = [\n",
        "    c.depth(lambda x: x.operation.num_qubits == 2)\n",
        "    for c in isa_circuits_uni[::3]\n",
        "]\n",
        "meas_uni = [\n",
        "    sum(1 for instr in c.data if instr.operation.name == \"measure\")\n",
        "    for c in isa_circuits_uni[::3]\n",
        "]\n",
        "\n",
        "fig, axes = plt.subplots(1, 2, figsize=(12, 5))\n",
        "\n",
        "axes[0].plot(\n",
        "    distances, depths_2q_uni, \"o-.\", color=\"c\", label=\"Unitary (2Q depth)\"\n",
        ")\n",
        "axes[0].plot(\n",
        "    distances, depths_2q_dyn, \"o-.\", color=\"m\", label=\"Dynamic (2Q depth)\"\n",
        ")\n",
        "axes[0].set_xlabel(\"Number of qubits between control and target\")\n",
        "axes[0].set_ylabel(\"Two-qubit depth\")\n",
        "axes[0].grid(True, linestyle=\":\", linewidth=0.6, alpha=0.4)\n",
        "axes[0].legend()\n",
        "\n",
        "axes[1].plot(\n",
        "    distances, meas_uni, \"o-.\", color=\"c\", label=\"Unitary (# measurements)\"\n",
        ")\n",
        "axes[1].plot(\n",
        "    distances, meas_dyn, \"o-.\", color=\"m\", label=\"Dynamic (# measurements)\"\n",
        ")\n",
        "axes[1].set_xlabel(\"Number of qubits between control and target\")\n",
        "axes[1].set_ylabel(\"Number of measurements\")\n",
        "axes[1].grid(True, linestyle=\":\", linewidth=0.6, alpha=0.4)\n",
        "axes[1].legend()\n",
        "\n",
        "fig.suptitle(\"Scaling of Unitary vs Dynamic LRCX with Distance\", fontsize=12)\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55326bf8",
      "metadata": {},
      "source": [
        "Questo grafico della profondità dei due qubit evidenzia il vantaggio principale dell'LRCX implementato con circuiti dinamici: le prestazioni rimangono essenzialmente costanti all'aumentare della separazione tra i qubit di controllo e quelli di destinazione. Al contrario, l'implementazione unitaria cresce linearmente con la distanza a causa delle catene SWAP necessarie. La profondità cattura lo scaling logico delle operazioni a due qubit, mentre il conteggio delle misure riflette l'overhead aggiuntivo per i circuiti dinamici. Queste misure sono efficienti, poiché vengono eseguite in parallelo, ma introducono comunque un costo fisso sull'hardware odierno.\n",
        "\n",
        "Perché la fedeltà può essere peggiore oggi: L'aumento del tempo di circuito dovuto alle operazioni di misura e classiche spesso domina, soprattutto quando la separazione controllo-obiettivo è piccola. Ad esempio, la lunghezza media di lettura di un processore Heron r2 è di 2.280 ns, mentre la lunghezza del gate 2Q è di soli 68 ns.\n",
        "\n",
        "Con il miglioramento delle latenze di misura e classiche, ci aspettiamo che la scalatura a profondità costante e a misurazione costante dei circuiti dinamici produca chiari vantaggi in termini di fedeltà e di tempo di esecuzione su circuiti più grandi.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dae8ad4a",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti materiali:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  * [Circuiti dinamici di riferimento con coppie di Bell interrotte](/docs/tutorials/edc-cut-bell-pair-benchmarking)\n",
        "  * [Simulazione dell'Hamiltoniano di Ising con kick mediante circuiti dinamici](/docs/tutorials/dc-hex-ising)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cc5af2f9",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Riferimenti\n",
        "\n",
        "<a id=\"ref-1\" />\n",
        "\n",
        "\\[1] Entanglement efficiente a lungo raggio con circuiti dinamici, di *Elisa Bäumer, Vinay Tripathi, Derek S. Wang, Patrick Rall, Edward H. Chen, Swarnadeep Majumder, Alireza Seif, Zlatko K. Minev*. IBM Quantum, (2023).\n",
        "[https://arxiv.org/abs/2308.13065](https://arxiv.org/abs/2308.13065)\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": 240
  },
  "nbformat": 4,
  "nbformat_minor": 5
}