{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "f69d5853-e815-4754-894d-833017217572",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Evaluación comparativa en tiempo real para la selección de qubits\"\n",
        "description: \"Realice experimentos de caracterización en tiempo real y actualice las propiedades del backend para mejorar la selección de qubits al asignar un circuito a los qubits físicos en una QPU.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore sharex */}\n",
        "\n",
        "<span id=\"real-time-benchmarking-for-qubit-selection\" />\n",
        "\n",
        "# Evaluación comparativa en tiempo real para la selección de qubits\n",
        "\n",
        "*Estimación de uso: 4 minutos en un procesador Eagle r2 (NOTA: Esto es sólo una estimación. Su tiempo de ejecución puede variar)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "500dc8c9-a5d8-4ef1-932f-30e400d6bdde",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "Este tutorial muestra cómo ejecutar experimentos de caracterización en tiempo real y actualizar las propiedades del backend para mejorar la selección de qubits cuando se mapea un circuito a los qubits físicos en una QPU. Aprenderás los experimentos básicos de caracterización que se utilizan para determinar las propiedades de la QPU, cómo realizarlos en Qiskit y cómo actualizar las propiedades guardadas en el objeto backend que representa a la QPU en función de estos experimentos.\n",
        "\n",
        "Las propiedades notificadas por QPU se actualizan una vez al día, pero el sistema puede desviarse más rápido que el tiempo entre actualizaciones. Esto puede afectar a la fiabilidad de las rutinas de selección de qubits en la etapa `Layout` del gestor de pases, ya que estarían utilizando propiedades notificadas que no representan el estado actual de la QPU. Por esta razón, puede merecer la pena dedicar algo de tiempo de la QPU a experimentos de caracterización, que luego pueden utilizarse para actualizar las propiedades de la QPU utilizadas por la rutina `Layout` .\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0babd413-d91f-4fd7-a0f5-bb46ae0bbf5b",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de tener instalado lo siguiente:\n",
        "\n",
        "* Qiskit SDK v2.0 o posterior, con soporte [de visualización](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.40 o posterior ( `pip install qiskit-ibm-runtime` )\n",
        "* Experimentos Qiskit v0.12 o posterior ( `pip install qiskit-experiments` )\n",
        "* Biblioteca gráfica Rustworkx (`pip install rustworkx`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3df52d5f-806a-4846-849e-633706a96d0b",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuración\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "4766c18a-ba45-456b-8b78-6b6f1d214586",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_runtime import SamplerV2\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit.quantum_info import hellinger_fidelity\n",
        "from qiskit.transpiler import InstructionProperties\n",
        "\n",
        "\n",
        "from qiskit_experiments.library import (\n",
        "    T1,\n",
        "    T2Hahn,\n",
        "    LocalReadoutError,\n",
        "    StandardRB,\n",
        ")\n",
        "from qiskit_experiments.framework import BatchExperiment, ParallelExperiment\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import Session\n",
        "\n",
        "from datetime import datetime\n",
        "from collections import defaultdict\n",
        "import numpy as np\n",
        "import rustworkx\n",
        "import matplotlib.pyplot as plt\n",
        "import copy"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65d49ed2-0581-486e-9031-a08fa9bace92",
      "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",
        "Para evaluar la diferencia de rendimiento, consideramos un circuito que prepara un estado Bell a través de una cadena lineal de longitud variable. Se mide la fidelidad del estado Bell en los extremos de la cadena.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "64c25da9-a728-4ae4-a377-3078a1dc618d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/real-time-benchmarking-for-qubit-selection/extracted-outputs/64c25da9-a728-4ae4-a377-3078a1dc618d-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/real-time-benchmarking-for-qubit-selection/extracted-outputs/64c25da9-a728-4ae4-a377-3078a1dc618d-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "\n",
        "ideal_dist = {\"00\": 0.5, \"11\": 0.5}\n",
        "\n",
        "num_qubits_list = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 127]\n",
        "circuits = []\n",
        "for num_qubits in num_qubits_list:\n",
        "    circuit = QuantumCircuit(num_qubits, 2)\n",
        "    circuit.h(0)\n",
        "    for i in range(num_qubits - 1):\n",
        "        circuit.cx(i, i + 1)\n",
        "    circuit.barrier()\n",
        "    circuit.measure(0, 0)\n",
        "    circuit.measure(num_qubits - 1, 1)\n",
        "    circuits.append(circuit)\n",
        "\n",
        "circuits[-1].draw(output=\"mpl\", style=\"clifford\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "16948f21-a39b-4444-bf02-5f81331825c4",
      "metadata": {},
      "source": [
        "<span id=\"set-up-backend-and-coupling-map\" />\n",
        "\n",
        "### Configurar el backend y el mapa de acoplamiento\n",
        "\n",
        "En primer lugar, seleccione un backend\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "f968acca-9131-4f5d-aa74-70befcdda4f5",
      "metadata": {},
      "outputs": [],
      "source": [
        "# To run on hardware, select the backend with the fewest number of jobs in the queue\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=127\n",
        ")\n",
        "\n",
        "qubits = list(range(backend.num_qubits))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "90b0ddab-0d35-4784-8575-fe0505699197",
      "metadata": {},
      "source": [
        "A continuación, obtenga su mapa de acoplamiento\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "86d84baa-bd5e-4faf-9582-4568ab4c4a4e",
      "metadata": {},
      "outputs": [],
      "source": [
        "coupling_graph = backend.coupling_map.graph.to_undirected(multigraph=False)\n",
        "\n",
        "# Get unidirectional coupling map\n",
        "one_dir_coupling_map = coupling_graph.edge_list()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d48b5ba2-74ec-4484-9ba6-b0681ae32919",
      "metadata": {},
      "source": [
        "Para poder comparar simultáneamente tantas puertas de dos qubits como sea posible, separamos el mapa de acoplamiento en `layered_coupling_map`. Este objeto contiene una lista de capas donde cada capa es una lista de aristas en las que se pueden ejecutar puertas de dos qubits al mismo tiempo. También se denomina coloración de los bordes del mapa de acoplamiento.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "62b36ded-ab4e-414e-b146-ff522786a871",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Get layered coupling map\n",
        "edge_coloring = rustworkx.graph_bipartite_edge_color(coupling_graph)\n",
        "layered_coupling_map = defaultdict(list)\n",
        "for edge_idx, color in edge_coloring.items():\n",
        "    layered_coupling_map[color].append(\n",
        "        coupling_graph.get_edge_endpoints_by_index(edge_idx)\n",
        "    )\n",
        "layered_coupling_map = [\n",
        "    sorted(layered_coupling_map[i])\n",
        "    for i in sorted(layered_coupling_map.keys())\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "875117af-8a2c-4aea-92d9-ffeee7ff37d5",
      "metadata": {},
      "source": [
        "<span id=\"characterization-experiments\" />\n",
        "\n",
        "### Experimentos de caracterización\n",
        "\n",
        "Se utiliza una serie de experimentos para caracterizar las principales propiedades de los qubits en una QPU. Se trata de $T_1$, $T_2$, error de lectura y error de puerta de uno y dos qubits. Resumiremos brevemente cuáles son estas propiedades y haremos referencia a los experimentos del paquete [`qiskit-experiments`](https://qiskit-community.github.io/qiskit-experiments/index.html) que se utilizan para caracterizarlas.\n",
        "\n",
        "<span id=\"t1\" />\n",
        "\n",
        "#### T1\n",
        "\n",
        "$T_1$ es el tiempo característico que tarda un qubit excitado en caer al estado de reposo debido a los procesos de decoherencia de amortiguación de amplitud. En un [experimento $T_1$](https://qiskit-community.github.io/qiskit-experiments/manuals/characterization/t1.html), medimos un qubit excitado tras un retardo. Cuanto mayor sea el tiempo de retardo, más probable es que el qubit caiga al estado de masa. El objetivo del experimento es caracterizar la tasa de decaimiento del qubit hacia el hacia el estado básico.\n",
        "\n",
        "<span id=\"t2\" />\n",
        "\n",
        "#### T2\n",
        "\n",
        "$T_2$ representa el tiempo necesario para que la proyección del vector Bloch de un qubit en el plano XY caiga hasta aproximadamente el 37% ( $\\frac{1}{e}$ ) de su amplitud inicial debido a los procesos de decoherencia de desfase de su amplitud inicial debido a procesos de decoherencia. En un [experimento de Eco Hahn $T_2$](https://qiskit-community.github.io/qiskit-experiments/manuals/characterization/t2hahn.html), podemos estimar la velocidad de esta desintegración.\n",
        "\n",
        "<span id=\"state-preparation-and-measurement-spam-error-characterization\" />\n",
        "\n",
        "#### Caracterización del error de preparación y medición del estado (SPAM)\n",
        "\n",
        "En un [experimento de caracterización de errores SPAM](https://qiskit-community.github.io/qiskit-experiments/manuals/measurement/readout_mitigation.html), los qubit se preparan en un estado determinado ( $\\vert 0 \\rangle$ o $\\vert 1 \\rangle$ ) y se miden. La probabilidad de medir un estado distinto del preparado da entonces la probabilidad del error.\n",
        "\n",
        "<span id=\"single-qubit-and-two-qubit-randomized-benchmarking\" />\n",
        "\n",
        "#### Evaluación comparativa aleatoria de un solo qubit y dos qubits\n",
        "\n",
        "[La evaluación comparativa aleatoria (RB](https://qiskit-community.github.io/qiskit-experiments/manuals/verification/randomized_benchmarking.html) ) es un protocolo popular para caracterizar la tasa de error de los procesadores cuánticos procesadores cuánticos. Un experimento RB consiste en la generación de circuitos Clifford en los qubits dados de forma que el unitario calculado por los circuitos sea la identidad. Después de ejecutar los circuitos, se cuenta el número de disparos que dan lugar a un error (es decir, una salida diferente del estado fundamental), y a partir de estos datos se pueden inferir estimaciones de error para el dispositivo cuántico, calculando el Error Por Clifford.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "9d57c42d-7a91-4e79-bc6c-6e579da929f8",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Create T1 experiments on all qubit in parallel\n",
        "t1_exp = ParallelExperiment(\n",
        "    [\n",
        "        T1(\n",
        "            physical_qubits=[qubit],\n",
        "            delays=[1e-6, 20e-6, 40e-6, 80e-6, 200e-6, 400e-6],\n",
        "        )\n",
        "        for qubit in qubits\n",
        "    ],\n",
        "    backend,\n",
        "    analysis=None,\n",
        ")\n",
        "\n",
        "# Create T2-Hahn experiments on all qubit in parallel\n",
        "t2_exp = ParallelExperiment(\n",
        "    [\n",
        "        T2Hahn(\n",
        "            physical_qubits=[qubit],\n",
        "            delays=[1e-6, 20e-6, 40e-6, 80e-6, 200e-6, 400e-6],\n",
        "        )\n",
        "        for qubit in qubits\n",
        "    ],\n",
        "    backend,\n",
        "    analysis=None,\n",
        ")\n",
        "\n",
        "# Create readout experiments on all qubit in parallel\n",
        "readout_exp = LocalReadoutError(qubits)\n",
        "\n",
        "# Create single-qubit RB experiments on all qubit in parallel\n",
        "singleq_rb_exp = ParallelExperiment(\n",
        "    [\n",
        "        StandardRB(\n",
        "            physical_qubits=[qubit], lengths=[10, 100, 500], num_samples=10\n",
        "        )\n",
        "        for qubit in qubits\n",
        "    ],\n",
        "    backend,\n",
        "    analysis=None,\n",
        ")\n",
        "\n",
        "# Create two-qubit RB experiments on the three layers of disjoint edges of the heavy-hex\n",
        "twoq_rb_exp_batched = BatchExperiment(\n",
        "    [\n",
        "        ParallelExperiment(\n",
        "            [\n",
        "                StandardRB(\n",
        "                    physical_qubits=pair,\n",
        "                    lengths=[10, 50, 100],\n",
        "                    num_samples=10,\n",
        "                )\n",
        "                for pair in layer\n",
        "            ],\n",
        "            backend,\n",
        "            analysis=None,\n",
        "        )\n",
        "        for layer in layered_coupling_map\n",
        "    ],\n",
        "    backend,\n",
        "    flatten_results=True,\n",
        "    analysis=None,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cad4f8d3-c2d5-4bb5-92be-432a4573e14a",
      "metadata": {},
      "source": [
        "<span id=\"qpu-properties-over-time\" />\n",
        "\n",
        "### Propiedades de la QPU a lo largo del tiempo\n",
        "\n",
        "Si observamos las propiedades QPU notificadas a lo largo del tiempo (a continuación consideraremos una sola semana), veremos cómo pueden fluctuar en la escala de un solo día. Pueden producirse pequeñas fluctuaciones incluso en un mismo día. En este caso, las propiedades notificadas (actualizadas una vez al día) no reflejarán con exactitud el estado actual de la QPU. Además, si un trabajo se transpila localmente (utilizando las propiedades actuales notificadas) y se envía, pero no se ejecuta hasta más tarde (minutos o días), puede correr el riesgo de haber utilizado propiedades obsoletas para la selección de qubits en el paso de transpilación. Esto pone de relieve la importancia de disponer de información actualizada sobre la QPU en el momento de la ejecución. En primer lugar, recuperemos las propiedades en un intervalo de tiempo determinado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "af1b6722-e77b-436a-bbcd-f272b95bb28c",
      "metadata": {},
      "outputs": [],
      "source": [
        "instruction_2q_name = \"cz\"  # set the name of the default 2q of the device\n",
        "errors_list = []\n",
        "for day_idx in range(10, 17):\n",
        "    calibrations_time = datetime(\n",
        "        year=2025, month=8, day=day_idx, hour=0, minute=0, second=0\n",
        "    )\n",
        "    targer_hist = backend.target_history(datetime=calibrations_time)\n",
        "\n",
        "    t1_dict, t2_dict = {}, {}\n",
        "    for qubit in range(targer_hist.num_qubits):\n",
        "        t1_dict[qubit] = targer_hist.qubit_properties[qubit].t1\n",
        "        t2_dict[qubit] = targer_hist.qubit_properties[qubit].t2\n",
        "\n",
        "    errors_dict = {\n",
        "        \"1q\": targer_hist[\"sx\"],\n",
        "        \"2q\": targer_hist[f\"{instruction_2q_name}\"],\n",
        "        \"spam\": targer_hist[\"measure\"],\n",
        "        \"t1\": t1_dict,\n",
        "        \"t2\": t2_dict,\n",
        "    }\n",
        "\n",
        "    errors_list.append(errors_dict)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ac75df1b-f689-475c-94a2-d70d85b1f8ca",
      "metadata": {},
      "source": [
        "A continuación, vamos a trazar los valores\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "e0ba509d-e0e0-438b-aedf-5e01919c7d4f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/real-time-benchmarking-for-qubit-selection/extracted-outputs/e0ba509d-e0e0-438b-aedf-5e01919c7d4f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axs = plt.subplots(5, 1, figsize=(10, 20), sharex=False)\n",
        "\n",
        "\n",
        "# Plot for T1 values\n",
        "for qubit in range(targer_hist.num_qubits):\n",
        "    t1s = []\n",
        "    for errors_dict in errors_list:\n",
        "        t1_dict = errors_dict[\"t1\"]\n",
        "        try:\n",
        "            t1s.append(t1_dict[qubit] / 1e-6)\n",
        "        except:\n",
        "            print(f\"missing t1 data for qubit {qubit}\")\n",
        "\n",
        "    axs[0].plot(t1s)\n",
        "\n",
        "axs[0].set_title(\"T1\")\n",
        "axs[0].set_ylabel(r\"Time ($\\mu s$)\")\n",
        "axs[0].set_xlabel(\"Days\")\n",
        "\n",
        "# Plot for T2 values\n",
        "for qubit in range(targer_hist.num_qubits):\n",
        "    t2s = []\n",
        "    for errors_dict in errors_list:\n",
        "        t2_dict = errors_dict[\"t2\"]\n",
        "        try:\n",
        "            t2s.append(t2_dict[qubit] / 1e-6)\n",
        "        except:\n",
        "            print(f\"missing t2 data for qubit {qubit}\")\n",
        "\n",
        "    axs[1].plot(t2s)\n",
        "\n",
        "axs[1].set_title(\"T2\")\n",
        "axs[1].set_ylabel(r\"Time ($\\mu s$)\")\n",
        "axs[1].set_xlabel(\"Days\")\n",
        "\n",
        "# Plot SPAM values\n",
        "for qubit in range(targer_hist.num_qubits):\n",
        "    spams = []\n",
        "    for errors_dict in errors_list:\n",
        "        spam_dict = errors_dict[\"spam\"]\n",
        "        spams.append(spam_dict[tuple([qubit])].error)\n",
        "\n",
        "    axs[2].plot(spams)\n",
        "\n",
        "axs[2].set_title(\"SPAM Errors\")\n",
        "axs[2].set_ylabel(\"Error Rate\")\n",
        "axs[2].set_xlabel(\"Days\")\n",
        "\n",
        "# Plot 1Q Gate Errors\n",
        "for qubit in range(targer_hist.num_qubits):\n",
        "    oneq_gates = []\n",
        "    for errors_dict in errors_list:\n",
        "        oneq_gate_dict = errors_dict[\"1q\"]\n",
        "        oneq_gates.append(oneq_gate_dict[tuple([qubit])].error)\n",
        "\n",
        "    axs[3].plot(oneq_gates)\n",
        "\n",
        "axs[3].set_title(\"1Q Gate Errors\")\n",
        "axs[3].set_ylabel(\"Error Rate\")\n",
        "axs[3].set_xlabel(\"Days\")\n",
        "\n",
        "# Plot 2Q Gate Errors\n",
        "for pair in one_dir_coupling_map:\n",
        "    twoq_gates = []\n",
        "    for errors_dict in errors_list:\n",
        "        twoq_gate_dict = errors_dict[\"2q\"]\n",
        "        twoq_gates.append(twoq_gate_dict[pair].error)\n",
        "\n",
        "    axs[4].plot(twoq_gates)\n",
        "\n",
        "axs[4].set_title(\"2Q Gate Errors\")\n",
        "axs[4].set_ylabel(\"Error Rate\")\n",
        "axs[4].set_xlabel(\"Days\")\n",
        "\n",
        "plt.subplots_adjust(hspace=0.5)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a3343934-9273-457c-900b-2004b3aa9d0c",
      "metadata": {},
      "source": [
        "Se puede ver que a lo largo de varios días algunas de las propiedades de los qubits pueden cambiar considerablemente. Esto subraya la importancia de disponer de información actualizada sobre el estado de la QPU para poder seleccionar los qubits con mejor rendimiento para un experimento.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0e53f0a5-7713-4915-ae47-c1aa0a4f17cd",
      "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 tutorial no se realiza ninguna optimización de los circuitos ni de los operadores.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "862789c2-d17e-43ed-8e1b-2f37d7628ed2",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "## Paso 3: Ejecutar utilizando Qiskit primitives\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d88f925f-3ffd-43de-ac1f-2c5ba3b8cd96",
      "metadata": {},
      "source": [
        "<span id=\"execute-a-quantum-circuit-with-default-qubit-selection\" />\n",
        "\n",
        "### Ejecutar un circuito cuántico con selección predeterminada de qubits\n",
        "\n",
        "Como resultado de referencia del rendimiento, ejecutaremos un circuito cuántico en una QPU utilizando los qubits por defecto, que son los qubits seleccionados con las propiedades backend solicitadas. Utilizaremos `optimization_level = 3`.  Esta configuración incluye la optimización de transpilación más avanzada y utiliza las propiedades del objetivo (como los errores de operación) para seleccionar los qubits de mejor rendimiento para la ejecución.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "5c0d09ad-2e6a-4067-8034-8df92a475ff1",
      "metadata": {},
      "outputs": [],
      "source": [
        "pm = generate_preset_pass_manager(target=backend.target, optimization_level=3)\n",
        "isa_circuits = pm.run(circuits)\n",
        "initial_qubits = [\n",
        "    [\n",
        "        idx\n",
        "        for idx, qb in circuit.layout.initial_layout.get_physical_bits().items()\n",
        "        if qb._register.name != \"ancilla\"\n",
        "    ]\n",
        "    for circuit in isa_circuits\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6d4fb04c-a5cf-48e8-a759-dde64fd751ac",
      "metadata": {},
      "source": [
        "<span id=\"execute-a-quantum-circuit-with-real-time-qubit-selection\" />\n",
        "\n",
        "### Ejecutar un circuito cuántico con selección de qubits en tiempo real\n",
        "\n",
        "En esta sección, investigaremos la importancia de disponer de información actualizada sobre las propiedades de los qubits de la QPU para obtener resultados óptimos. En primer lugar, llevaremos a cabo un conjunto completo de experimentos de caracterización de QPU ( $T_1$, $T_2$, SPAM, RB de un qubit y RB de dos qubits), que luego podremos utilizar para actualizar las propiedades del backend. Esto permite al gestor de pases seleccionar qubits para la ejecución basándose en información reciente sobre la QPU, lo que posiblemente mejore el rendimiento de la ejecución. En segundo lugar, ejecutamos el circuito de pares de Bell y comparamos la fidelidad obtenida después de seleccionar los qubits con las propiedades QPU actualizadas con la fidelidad que obtuvimos antes cuando utilizamos las propiedades informadas por defecto para la selección de qubits.\n",
        "\n",
        "<Admonition type=\"caution\">\n",
        "  Tenga en cuenta que algunos de los experimentos de caracterización pueden fallar cuando la rutina de ajuste no puede ajustar una curva a los datos medidos. Si ves advertencias procedentes de estos experimentos, inspecciónalos para entender qué caracterización falló en qué qubits, e intenta ajustar los parámetros del experimento (como los tiempos para $T_1$, $T_2$, o la longitud del número de experimentos RB).\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "a8266467-9d60-411f-89dd-8cad6558f588",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Prepare characterization experiments\n",
        "batches = [t1_exp, t2_exp, readout_exp, singleq_rb_exp, twoq_rb_exp_batched]\n",
        "batches_exp = BatchExperiment(batches, backend)  # , analysis=None)\n",
        "run_options = {\"shots\": 1e3, \"dynamic\": False}\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    sampler = SamplerV2(mode=session)\n",
        "\n",
        "    # Run characterization experiments\n",
        "    batches_exp_data = batches_exp.run(\n",
        "        sampler=sampler, **run_options\n",
        "    ).block_for_results()\n",
        "\n",
        "    EPG_sx_result_list = batches_exp_data.analysis_results(\"EPG_sx\")\n",
        "    EPG_sx_result_q_indices = [\n",
        "        result.device_components.index for result in EPG_sx_result_list\n",
        "    ]\n",
        "    EPG_x_result_list = batches_exp_data.analysis_results(\"EPG_x\")\n",
        "    EPG_x_result_q_indices = [\n",
        "        result.device_components.index for result in EPG_x_result_list\n",
        "    ]\n",
        "    T1_result_list = batches_exp_data.analysis_results(\"T1\")\n",
        "    T1_result_q_indices = [\n",
        "        result.device_components.index for result in T1_result_list\n",
        "    ]\n",
        "\n",
        "    T2_result_list = batches_exp_data.analysis_results(\"T2\")\n",
        "    T2_result_q_indices = [\n",
        "        result.device_components.index for result in T2_result_list\n",
        "    ]\n",
        "\n",
        "    Readout_result_list = batches_exp_data.analysis_results(\n",
        "        \"Local Readout Mitigator\"\n",
        "    )\n",
        "\n",
        "    EPG_2q_result_list = batches_exp_data.analysis_results(\n",
        "        f\"EPG_{instruction_2q_name}\"\n",
        "    )\n",
        "\n",
        "    # Update target properties\n",
        "    target = copy.deepcopy(backend.target)\n",
        "    for i in range(target.num_qubits - 1):\n",
        "        qarg = (i,)\n",
        "\n",
        "        if qarg in EPG_sx_result_q_indices:\n",
        "            target.update_instruction_properties(\n",
        "                instruction=\"sx\",\n",
        "                qargs=qarg,\n",
        "                properties=InstructionProperties(\n",
        "                    error=EPG_sx_result_list[i].value.nominal_value\n",
        "                ),\n",
        "            )\n",
        "        if qarg in EPG_x_result_q_indices:\n",
        "            target.update_instruction_properties(\n",
        "                instruction=\"x\",\n",
        "                qargs=qarg,\n",
        "                properties=InstructionProperties(\n",
        "                    error=EPG_x_result_list[i].value.nominal_value\n",
        "                ),\n",
        "            )\n",
        "\n",
        "        err_mat = Readout_result_list.value.assignment_matrix(i)\n",
        "        readout_assignment_error = (\n",
        "            err_mat[0, 1] + err_mat[1, 0]\n",
        "        ) / 2  # average readout error\n",
        "        target.update_instruction_properties(\n",
        "            instruction=\"measure\",\n",
        "            qargs=qarg,\n",
        "            properties=InstructionProperties(error=readout_assignment_error),\n",
        "        )\n",
        "\n",
        "        if qarg in T1_result_q_indices:\n",
        "            target.qubit_properties[i].t1 = T1_result_list[\n",
        "                i\n",
        "            ].value.nominal_value\n",
        "        if qarg in T2_result_q_indices:\n",
        "            target.qubit_properties[i].t2 = T2_result_list[\n",
        "                i\n",
        "            ].value.nominal_value\n",
        "\n",
        "    for pair_idx, pair in enumerate(one_dir_coupling_map):\n",
        "        qarg = tuple(pair)\n",
        "        try:\n",
        "            target.update_instruction_properties(\n",
        "                instruction=instruction_2q_name,\n",
        "                qargs=qarg,\n",
        "                properties=InstructionProperties(\n",
        "                    error=EPG_2q_result_list[pair_idx].value.nominal_value\n",
        "                ),\n",
        "            )\n",
        "        except:\n",
        "            target.update_instruction_properties(\n",
        "                instruction=instruction_2q_name,\n",
        "                qargs=qarg[::-1],\n",
        "                properties=InstructionProperties(\n",
        "                    error=EPG_2q_result_list[pair_idx].value.nominal_value\n",
        "                ),\n",
        "            )\n",
        "\n",
        "    # transpile circuits to updated target\n",
        "    pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "    isa_circuit_updated = pm.run(circuits)\n",
        "    updated_qubits = [\n",
        "        [\n",
        "            idx\n",
        "            for idx, qb in circuit.layout.initial_layout.get_physical_bits().items()\n",
        "            if qb._register.name != \"ancilla\"\n",
        "        ]\n",
        "        for circuit in isa_circuit_updated\n",
        "    ]\n",
        "\n",
        "    n_trials = 3  # run multiple trials to see variations\n",
        "\n",
        "    # interleave circuits\n",
        "    interleaved_circuits = []\n",
        "    for original_circuit, updated_circuit in zip(\n",
        "        isa_circuits, isa_circuit_updated\n",
        "    ):\n",
        "        interleaved_circuits.append(original_circuit)\n",
        "        interleaved_circuits.append(updated_circuit)\n",
        "\n",
        "    # Run circuits\n",
        "    # Set simple error suppression/mitigation options\n",
        "    sampler.options.dynamical_decoupling.enable = True\n",
        "    sampler.options.dynamical_decoupling.sequence_type = \"XY4\"\n",
        "\n",
        "    job_interleaved = sampler.run(interleaved_circuits * n_trials)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d72e021a-3ea6-4ed2-829f-e59ce8017ef4",
      "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",
        "Por último, comparemos la fidelidad del estado de Bell obtenido en las dos configuraciones diferentes:\n",
        "\n",
        "* `original`es decir, con los qubits por defecto elegidos por el transpilador basándose en las propiedades notificadas del backend.\n",
        "* `updated`es decir, con los qubits elegidos en función de las propiedades actualizadas del backend una vez realizados los experimentos de caracterización.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "5a19a5d1-5daf-4d65-9785-8f8724853821",
      "metadata": {},
      "outputs": [],
      "source": [
        "results = job_interleaved.result()\n",
        "all_fidelity_list, all_fidelity_updated_list = [], []\n",
        "for exp_idx in range(n_trials):\n",
        "    fidelity_list, fidelity_updated_list = [], []\n",
        "\n",
        "    for idx, num_qubits in enumerate(num_qubits_list):\n",
        "        pub_result_original = results[\n",
        "            2 * exp_idx * len(num_qubits_list) + 2 * idx\n",
        "        ]\n",
        "        pub_result_updated = results[\n",
        "            2 * exp_idx * len(num_qubits_list) + 2 * idx + 1\n",
        "        ]\n",
        "\n",
        "        fid = hellinger_fidelity(\n",
        "            ideal_dist, pub_result_original.data.c.get_counts()\n",
        "        )\n",
        "        fidelity_list.append(fid)\n",
        "\n",
        "        fid_up = hellinger_fidelity(\n",
        "            ideal_dist, pub_result_updated.data.c.get_counts()\n",
        "        )\n",
        "        fidelity_updated_list.append(fid_up)\n",
        "    all_fidelity_list.append(fidelity_list)\n",
        "    all_fidelity_updated_list.append(fidelity_updated_list)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "656ec97a-3fd9-4635-9a98-1c5589761689",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/real-time-benchmarking-for-qubit-selection/extracted-outputs/656ec97a-3fd9-4635-9a98-1c5589761689-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "plt.figure(figsize=(8, 6))\n",
        "plt.errorbar(\n",
        "    num_qubits_list,\n",
        "    np.mean(all_fidelity_list, axis=0),\n",
        "    yerr=np.std(all_fidelity_list, axis=0),\n",
        "    fmt=\"o-.\",\n",
        "    label=\"original\",\n",
        "    color=\"b\",\n",
        ")\n",
        "# plt.plot(num_qubits_list, fidelity_list, '-.')\n",
        "plt.errorbar(\n",
        "    num_qubits_list,\n",
        "    np.mean(all_fidelity_updated_list, axis=0),\n",
        "    yerr=np.std(all_fidelity_updated_list, axis=0),\n",
        "    fmt=\"o-.\",\n",
        "    label=\"updated\",\n",
        "    color=\"r\",\n",
        ")\n",
        "# plt.plot(num_qubits_list, fidelity_updated_list, '-.')\n",
        "plt.xlabel(\"Chain length\")\n",
        "plt.xticks(num_qubits_list)\n",
        "plt.ylabel(\"Fidelity\")\n",
        "plt.title(\"Bell pair fidelity at the edge of N-qubits chain\")\n",
        "plt.legend()\n",
        "plt.grid(\n",
        "    alpha=0.2,\n",
        "    linestyle=\"-.\",\n",
        ")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3705d85c-e11d-4a8f-bfae-20bd501124d8",
      "metadata": {},
      "source": [
        "No todas las ejecuciones mostrarán una mejora en el rendimiento debido a la caracterización en tiempo real, y con el aumento de la longitud de la cadena, y por lo tanto menos libertad para elegir qubits físicos, la importancia de la información actualizada del dispositivo se vuelve menos sustancial. Sin embargo, es una buena práctica recopilar nuevos datos sobre las propiedades del dispositivo para comprender su rendimiento. Ocasionalmente, los sistemas transitorios de dos niveles pueden afectar al rendimiento de algunos de los qubits. Los datos en tiempo real pueden informarnos de cuándo se producen tales acontecimientos y ayudarnos a evitar fallos experimentales en tales casos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cd81b208-b13b-4988-854e-1741408f36f3",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Requerimiento de intervención\">\n",
        "  Intente aplicar este método a sus ejecuciones y determine el beneficio que obtiene También puedes probar y ver cuántas mejoras obtienes con diferentes backends.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b94755c1-1c19-434a-96b9-b83922b5d63c",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## Encuesta tutorial\n",
        "\n",
        "Responda a esta breve encuesta para darnos su opinión sobre este tutorial. Su opinión nos ayudará a mejorar nuestra oferta de contenidos y la experiencia de los usuarios.\n",
        "\n",
        "[Enlace a la encuesta](https://your.feedback.ibm.com/jfe/form/SV_0w6FZ9QrWkKfTQq)\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"
    },
    "vscode": {
      "interpreter": {
        "hash": "72460b19b35c51a2148375679c8e282bc3cd3c5550c209f0a38b7d09cded82d9"
      }
    },
    "hours": 2.5,
    "qpuSeconds": 240
  },
  "nbformat": 4,
  "nbformat_minor": 4
}