{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "44d97249",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Entrelazamiento de largo alcance con circuitos dinámicos\"\n",
        "description: \"En este tutorial se implementa un CNOT de largo alcance utilizando circuitos dinámicos con pares de Bell, mediciones y alimentación directa, y se compara con un enfoque unitario directo.\"\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",
        "# Entrelazamiento de largo alcance con circuitos dinámicos\n",
        "\n",
        "*Estimación de uso: 4 minutos en un procesador Heron r2. (NOTA: Esto es sólo una estimación. Su tiempo de ejecución puede variar)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "57f65bca",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados del aprendizaje\n",
        "\n",
        "Una vez completado este tutorial, habrás aprendido lo siguiente:\n",
        "\n",
        "* Cómo implementar una puerta CNOT de largo alcance utilizando circuitos dinámicos con mediciones en el medio del circuito (MCM) y alimentación directa clásica;\n",
        "* Cómo implementar la puerta equivalente mediante un enfoque unitario basado en SWAP;\n",
        "* Cómo comparar ambos enfoques midiendo la fidelidad de las puertas en función de la distancia entre qubits.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "748eee3b",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Requisitos previos\n",
        "\n",
        "Recomendamos a los usuarios que se familiaricen con los siguientes temas antes de seguir este tutorial:\n",
        "\n",
        "* [Conceptos básicos de la computación cuántica](/learning/courses/basics-of-quantum-information), incluidos los estados de Bell, el entrelazamiento y las puertas cuánticas;\n",
        "* Conocimiento de [los circuitos dinámicos](/docs/guides/classical-feedforward-and-control-flow) (mediciones en el punto medio del circuito y control clásico por anticipación);\n",
        "* Conocimientos básicos de [Qiskit SDK](/docs/guides) y [Qiskit Runtime](/docs/guides/compute-services#qiskit-runtime), así como acceso a una [cuenta](/docs/guides/cloud-setup) de IBM Quantum®.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b05b669f",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "El entrelazamiento a larga distancia entre qubits distantes es un reto en dispositivos con conectividad limitada. Este tutorial muestra cómo los circuitos dinámicos pueden generar dicho entrelazamiento mediante la implementación de una puerta X controlada de largo alcance (LRCX) utilizando un protocolo basado en mediciones.\n",
        "\n",
        "Siguiendo el planteamiento de Elisa Bäumer et al. en [1](#ref-1), el método utiliza la medición en mitad del circuito y el feedforward para conseguir puertas de profundidad constante independientemente de la separación entre qubits. Crea pares de Bell intermedios, mide un qubit de cada par y aplica puertas condicionadas clásicas para propagar el entrelazamiento a través del dispositivo. Esto evita largas cadenas SWAP, reduciendo tanto la profundidad del circuito como la exposición a errores de puerta de dos qubits.\n",
        "\n",
        "En este cuaderno, adaptamos el protocolo al hardware de IBM Quantum s y evaluamos su rendimiento en función de la distancia entre el control y el objetivo, comparándolo con una referencia basada en SWAP unitario.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c6cd3174",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de que tienes instalado lo siguiente:\n",
        "\n",
        "* Qiskit SDK v2.0 o posterior, con soporte [de visualización](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.37 o posterior (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer v0.17 o posterior (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "39e4440a",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuración\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",
        "## Ejemplo de simulador a pequeña escala\n",
        "\n",
        "Antes de ejecutar el código en la QPU real, verificamos que tanto los circuitos dinámicos como los unitarios generen un estado de Bell ideal en un simulador sin ruido. Utilizamos el modo « Qiskit Runtime`Sampler` » con `AerSimulator` el modo de fondo «as», a una distancia mínima de 6.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28145093",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Paso 1: Asignar entradas clásicas a un problema cuántico\n",
        "\n",
        "Ahora implementamos una puerta CNOT de largo alcance entre dos qubits distantes, siguiendo la construcción de circuito dinámico que se muestra a continuación (adaptada de la fig. 1a en la ref. [1](#ref-1) ). La idea fundamental consiste en utilizar un «bus» de qubits auxiliares, inicializados en $|0\\rangle$, para mediar en la teletransportación mediante puertas de largo alcance.\n",
        "\n",
        "![Circuito CNOT de largo alcance](https://quantum.cloud.ibm.com/docs/images/tutorials/long-range-entanglement/dynamic_vs_unitary_long_range_illustration.avif)\n",
        "\n",
        "Como se ilustra en la figura, el proceso funciona del siguiente modo:\n",
        "\n",
        "1. Prepara una cadena de pares de Bell que conecte los qubits de control y de destino a través de ancilas intermedias.\n",
        "2. Realizar mediciones de Bell entre qubits vecinos no enredados, intercambiando el enredo paso a paso hasta que el control y el objetivo compartan un par de Bell.\n",
        "3. Utiliza este par de Bell para el teletransporte de puertas, convirtiendo un CNOT local en un CNOT determinista de largo alcance en profundidad constante.\n",
        "\n",
        "Este enfoque sustituye las largas cadenas SWAP por un protocolo de profundidad constante, lo que reduce la exposición a errores de puerta de dos qubits y hace que la operación sea escalable con el tamaño del dispositivo.\n",
        "\n",
        "A continuación, veremos primero la implementación dinámica del circuito LRCX. Al final, también proporcionaremos una implementación basada en unitarios a modo de comparación, para destacar las ventajas de los circuitos dinámicos en este entorno.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89597fcf",
      "metadata": {},
      "source": [
        "<span id=\"initialize-circuit\" />\n",
        "\n",
        "#### Inicializar el circuito\n",
        "\n",
        "Comenzamos con un problema cuántico sencillo que nos servirá de referencia. En concreto, inicializamos un circuito con un qubit de control en el índice 0 y le aplicamos una puerta de Hadamard. Esto da lugar a un estado de superposición que, tras una operación X controlada, genera un estado de Bell $(|00\\rangle + |11\\rangle)/\\sqrt{2}$ entre los qubits de control y de destino.\n",
        "\n",
        "En esta fase, todavía no estamos construyendo la X controlada de largo alcance (LRCX) propiamente dicha. En su lugar, nuestro objetivo es definir un circuito inicial claro y mínimo que destaque el papel del LRCX. En el Paso 2, mostraremos cómo el LRCX puede implementarse como una optimización utilizando circuitos dinámicos, y compararemos su rendimiento frente a un equivalente unitario. Es importante destacar que el protocolo LRCX puede aplicarse a cualquier circuito inicial. Aquí utilizamos esta sencilla configuración Hadamard para mayor claridad de la demostración.\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",
        "### Paso 2: Optimizar el problema para la ejecución en hardware cuántico\n",
        "\n",
        "En este paso, mostramos cómo construir el circuito LRCX utilizando circuitos dinámicos. El objetivo es optimizar el circuito para su ejecución en hardware reduciendo la profundidad en comparación con una implementación puramente unitaria. Para ilustrar las ventajas, mostraremos tanto la construcción LRCX dinámica como su equivalente unitario, y posteriormente compararemos su rendimiento tras la transpilación. Es importante destacar que, aunque aquí aplicamos el LRCX a un problema sencillo inicializado con Hadamard, el protocolo puede aplicarse a cualquier circuito en el que se requiera un CNOT de largo alcance.\n",
        "\n",
        "<span id=\"prepare-bell-pairs\" />\n",
        "\n",
        "#### Preparar pares de campanas\n",
        "\n",
        "Comenzamos creando una cadena de pares de Bell a lo largo de la ruta entre los qubits de control y de destino. Si la distancia es impar, primero aplicamos un CNOT desde el control a su vecino, que es el CNOT que se teletransportará. Para una distancia par, este CNOT se aplicará después del paso de preparación del par de campanas. A continuación, la cadena de pares de Bell entrelaza pares sucesivos de qubits, estableciendo el recurso necesario para transportar la información de control a través del 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",
        "#### Mide los pares de qubits adyacentes en la base de Bell\n",
        "\n",
        "A continuación, medimos qubits vecinos *no enredados* en la base de Bell (mediciones de dos qubits de $XX$ y $ZZ$ ). Esto crea un par de Bell de largo alcance entre el qubit objetivo y el qubit adyacente al control (hasta correcciones de Pauli, que se implementarán mediante feedforward en el siguiente paso). Paralelamente, implementamos la medida de enredo que teletransporta la puerta CNOT para que actúe sobre el qubit objetivo 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",
        "#### Aplicar correcciones de alimentación directa para corregir los operadores de subproductos de Pauli\n",
        "\n",
        "Las mediciones de la base de Bell introducen subproductos de Pauli que deben corregirse utilizando los resultados registrados. Esto se hace en dos pasos. En primer lugar, tenemos que calcular la paridad de todas las mediciones de $ZZ$, que luego se utiliza para aplicar condicionalmente una puerta $X$ al qubit objetivo. Asimismo, se calcula la paridad de las mediciones de $XX$ y se utiliza para aplicar condicionalmente una puerta $Z$ al qubit de control.\n",
        "\n",
        "Con el nuevo marco de expresión clásica de Qiskit, estas paridades pueden calcularse directamente en la capa de procesamiento clásico del circuito. En lugar de aplicar una secuencia de puertas condicionales individuales para cada bit de medición, podemos construir una única expresión clásica que represente el XOR (paridad) de todos los resultados de medición relevantes. A continuación, esta expresión se utiliza como condición en un único bloque de `if_test` , lo que permite aplicar las puertas de corrección en profundidad constante. Este enfoque simplifica el circuito y garantiza que las correcciones no introduzcan una latencia adicional innecesaria.\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",
        "#### Control de la medida y qubits objetivo\n",
        "\n",
        "Definimos una función auxiliar que permite medir los qubits de control y de destino en las bases $XX$, $YY$, o $ZZ$. Para verificar el estado de Bell $(|00\\rangle + |11\\rangle)/\\sqrt{2}$, los valores de expectativa de $XX$ y $ZZ$ deben ser ambos $+1$, ya que son estabilizadores del estado. Aquí también se admite la medición $YY$, que se utilizará a continuación para calcular la fidelidad.\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",
        "#### Ponlo todo junto\n",
        "\n",
        "Combinamos los distintos pasos definidos anteriormente para crear una puerta de CX de largo alcance en ambos extremos de una línea unidimensional ( 1D ). Los pasos son los siguientes:\n",
        "\n",
        "* Inicialización del qubit de control en $|+\\rangle$\n",
        "* Preparación de los pares de campanas\n",
        "* Medición de pares de qubits vecinos\n",
        "* Aplicación de correcciones feedforward dependientes de los 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",
        "#### Implementación basada en unitaria intercambiando los qubits al centro\n",
        "\n",
        "A modo de comparación, examinamos primero el caso en el que se implementa una puerta CNOT de largo alcance utilizando conexiones entre vecinos más cercanos y puertas unitarias. En la siguiente figura, a la izquierda se muestra un circuito para una puerta CNOT de largo alcance que abarca una cadena de n qubits de tipo « 1D », en la que solo existen conexiones entre vecinos más cercanos. En el centro hay una descomposición unitaria equivalente que se puede implementar con puertas CNOT locales; la profundidad del circuito es de $O(n)$.\n",
        "\n",
        "![Circuito CNOT de largo alcance](https://quantum.cloud.ibm.com/docs/images/tutorials/long-range-entanglement/dynamic_vs_unitary_long_range_illustration.avif)\n",
        "\n",
        "El circuito del centro se puede realizar de la siguiente manera:\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": [
        "Ahora construye los circuitos que miden en las bases $XX$, $YY$ y $ZZ$, tal y como hicimos con los circuitos dinámicos anteriores.\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": [
        "Ahora que hemos construido tanto los circuitos dinámicos como los circuitos unitarios para un ejemplo a pequeña escala con `distance=6`, los compilamos para ejecutarlos primero en un simulador sin ruido.\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",
        "### Paso 3: Ejecutar utilizando Qiskit primitives\n",
        "\n",
        "Ahora podemos ejecutar el experimento en el backend del simulador sin ruido. Utilizamos el sampler « Qiskit Runtime » con el modo de fondo « AerSimulator » para ejecutar los circuitos.\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",
        "### Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado\n",
        "\n",
        "Una vez que los experimentos se han ejecutado con éxito, procedemos al posprocesamiento de los datos de medición para extraer métricas significativas.\n",
        "En este paso, hacemos lo siguiente:\n",
        "\n",
        "* Definir métricas de calidad para evaluar el rendimiento de la CX de largo alcance.\n",
        "* Calcular los valores de las expectativas de los operadores de Pauli a partir de los resultados brutos de las mediciones.\n",
        "* Utilízalos para calcular la fidelidad del estado Bell generado.\n",
        "\n",
        "En una simulación silenciosa, verificaremos que la métrica de fidelidad es « $1$ » para los circuitos construidos. En los experimentos realizados con QPU reales, este análisis ofrecerá una visión clara del rendimiento de los circuitos dinámicos en comparación con la implementación unitaria de referencia.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4ba303da",
      "metadata": {},
      "source": [
        "<span id=\"quality-metrics\" />\n",
        "\n",
        "#### Métricas de calidad\n",
        "\n",
        "Para evaluar el éxito del protocolo CX de largo alcance, medimos el grado de aproximación del estado de salida al estado de Bell ideal. Una forma práctica de cuantificarlo es calcular la fidelidad del estado utilizando los valores esperados de los operadores de Pauli. Podemos calcular la fidelidad de un estado de Bell en el estado de control y en el estado objetivo una vez que conocemos $\\braket{XX}$, $\\braket{YY}$ y $\\braket{ZZ}$. En concreto,\n",
        "\n",
        "$ F = \\frac{1}{4} (1 + \\braket{XX} - \\braket{YY} + \\braket{ZZ})$\n",
        "\n",
        "Para calcular estos valores de expectativa a partir de datos de medición brutos, definimos un conjunto de funciones de ayuda:\n",
        "\n",
        "* **`compute_ZZ_expectation`**: Dados los recuentos de medidas, calcula el valor de expectativa de un operador de Pauli de dos qubits en la base $Z$.\n",
        "* **`compute_fidelity`**: Combina los valores esperados de $XX$, $YY$, y $ZZ$ en la expresión de fidelidad anterior.\n",
        "* **`get_counts_from_bitarray`**: Utilidad para extraer recuentos de objetos de resultados de 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": [
        "Como era de esperar en una simulación sin ruido, las fidelidades tanto en los circuitos dinámicos como en los circuitos unitarios son de $1$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9ac23eb2",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Ejemplo de hardware a gran escala\n",
        "\n",
        "Aquí reunimos ahora todos estos detalles en un único flujo de trabajo a mayor escala, que luego se ejecuta en hardware cuántico real.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ad4224f2",
      "metadata": {},
      "source": [
        "<span id=\"generate-circuits-for-different-distances\" />\n",
        "\n",
        "### Generar circuitos para diferentes distancias\n",
        "\n",
        "Actualmente generamos circuitos CX de largo alcance para una variedad de separaciones entre qubits de hasta 60 qubits. Para cada distancia, construimos circuitos que miden en las bases $XX$, $YY$ y $ZZ$, que posteriormente se utilizarán para calcular las fidelidades.\n",
        "\n",
        "La lista de distancias incluye separaciones de corto y largo alcance, correspondiendo `distance = 0` a un vecino más cercano CX. Estas mismas distancias también se utilizarán para generar los circuitos unitarios correspondientes más adelante para su comparación.\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": [
        "Ahora que tenemos circuitos dinámicos y unitarios para una serie de distancias, estamos listos para la transpilación. Primero tenemos que seleccionar 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": [
        "El siguiente paso asegura que el backend soporta la instrucción `if_else` , necesaria para la nueva versión de circuitos dinámicos. Dado que esta función aún se encuentra en fase de acceso anticipado, añadimos explícitamente la dirección `IfElseOp` al objetivo backend si aún no está disponible.\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",
        "#### Utilice la cadena Layer Fidelity para seleccionar una cadena de 1D\n",
        "\n",
        "Como queremos comparar el rendimiento de los circuitos dinámicos y unitarios en una cadena 1D, utilizamos la cadena Layer Fidelity para seleccionar una topología lineal de la mejor cadena de qubits del dispositivo. Esto garantiza que ambos tipos de circuitos se transpilan bajo las mismas restricciones de conectividad, lo que permite una comparación justa de su rendimiento.\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",
        "### Visualizar los qubits utilizados para el circuito LRCX\n",
        "\n",
        "En esta sección, examinamos cómo se asigna el circuito LRCX al hardware. Comenzamos visualizando los qubits físicos utilizados en el circuito y, a continuación, estudiamos cómo influye la distancia control-objetivo en el trazado sobre el número de operaciones.\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": [
        "A continuación, llevamos a cabo el experimento en el backend real. También utilizamos el procesamiento por lotes para ejecutar el experimento de forma eficiente en múltiples ensayos. La realización de ensayos repetidos nos permite calcular promedios para realizar una comparación más precisa entre los métodos unitario y dinámico, así como cuantificar su variabilidad comparando las desviaciones entre las distintas series de ensayos.\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": [
        "Calculamos la fidelidad de los circuitos CX dinámicos de largo alcance. Para cada distancia, extraemos los resultados de las mediciones de las bases de datos $\\braket{XX}$, $\\braket{YY}$ y $\\braket{ZZ}$. Estos resultados se combinan utilizando las funciones auxiliares definidas anteriormente para calcular la fidelidad según $F = \\tfrac{1}{4} \\big( 1 + \\langle XX \\rangle - \\langle YY \\rangle + \\langle ZZ \\rangle \\big)$. Esto proporciona la fidelidad observada del protocolo ejecutado dinámicamente en cada distancia.\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": [
        "Ahora calculamos la fidelidad para los circuitos unitarios de largo alcance CX, y lo hacemos de la misma manera que hicimos anteriormente para los circuitos dinámicos.\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",
        "### Representar gráficamente los resultados\n",
        "\n",
        "Para apreciar los resultados de forma gráfica, en el siguiente gráfico se representan las fidelidades estimadas de las puertas, medidas a diferentes distancias entre los qubits entrelazados para cada método.\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": [
        "Según el gráfico de fidelidad anterior, el LRCX no superó sistemáticamente a la aplicación unitaria directa. De hecho, para separaciones control-objetivo cortas, el circuito unitario lograba una mayor fidelidad. Sin embargo, a mayores separaciones, el circuito dinámico empieza a lograr una mayor fidelidad que la implementación unitaria. Este comportamiento no es inesperado en el hardware actual: aunque los circuitos dinámicos reducen la profundidad del circuito al evitar largas cadenas SWAP, introducen un tiempo de circuito adicional debido a las mediciones en mitad del circuito, el feedforward clásico y los retardos de la ruta de control. La latencia añadida aumenta la decoherencia y los errores de lectura, lo que puede compensar el ahorro de profundidad en distancias cortas.\n",
        "\n",
        "No obstante, observamos un punto de cruce en el que el enfoque dinámico supera al unitario. Esto es un resultado directo del diferente escalado: la profundidad del circuito unitario crece linealmente con la distancia entre qubits, mientras que la profundidad del circuito dinámico permanece constante.\n",
        "\n",
        "**Puntos clave:**\n",
        "\n",
        "* **Beneficio inmediato de los circuitos dinámicos:** La principal motivación actual es reducir *la profundidad de dos qubits*, no necesariamente mejorar la fidelidad.\n",
        "* **Por qué la fidelidad puede ser peor hoy en día:** El aumento del tiempo de circuito de las operaciones de medición y clásicas suele dominar, sobre todo cuando la separación control-objetivo es pequeña.\n",
        "* **De cara al futuro:** A medida que mejore el hardware, en concreto una lectura más rápida, una latencia de control clásica más corta y una reducción de la sobrecarga en la mitad del circuito, deberíamos esperar que estas reducciones de profundidad y duración se traduzcan en ganancias de fidelidad mensurables.\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": [
        "Este gráfico de profundidad de dos qubits pone de manifiesto la principal ventaja del LRCX implementado con circuitos dinámicos: el rendimiento permanece esencialmente constante a medida que aumenta la separación entre los qubits de control y de destino. En cambio, la implementación unitaria crece linealmente con la distancia debido a las cadenas SWAP necesarias. La profundidad captura el escalado lógico de las operaciones de dos qubits, mientras que el recuento de medidas refleja la sobrecarga adicional de los circuitos dinámicos. Estas mediciones son eficientes, ya que se realizan en paralelo, pero siguen introduciendo un coste fijo en el hardware actual.\n",
        "\n",
        "Por qué la fidelidad puede ser peor hoy en día: El aumento del tiempo de circuito de las operaciones de medición y clásicas suele dominar, sobre todo cuando la separación control-objetivo es pequeña. Por ejemplo, la longitud media de lectura en un procesador Heron r2 es de 2.280 ns, mientras que su longitud de puerta 2Q es de sólo 68 ns.\n",
        "\n",
        "A medida que mejoren las latencias clásicas y de medición, esperamos que el escalado de profundidad constante y medición constante de los circuitos dinámicos produzca claras ventajas de fidelidad y tiempo de ejecución en circuitos más grandes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dae8ad4a",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximos pasos\n",
        "\n",
        "Si te ha parecido interesante este trabajo, quizá te interesen los siguientes materiales:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendaciones\">\n",
        "  * [Circuitos dinámicos de referencia con pares de Bell cortados](/docs/tutorials/edc-cut-bell-pair-benchmarking)\n",
        "  * [Simulación del hamiltoniano de Ising con «kick» mediante circuitos dinámicos](/docs/tutorials/dc-hex-ising)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cc5af2f9",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referencias\n",
        "\n",
        "<a id=\"ref-1\" />\n",
        "\n",
        "\\[1] Efficient Long-Range Entanglement using Dynamic Circuits, por *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
}