{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Evalúa la fidelidad del proceso QFT+M con Orbit, una función de Qiskit desarrollada por Quantum Elements\"\n",
        "description: \"Estimar la fidelidad del proceso QFT seguido de medición (QFT+M) en función del tamaño de los circuitos; comparar las implementaciones unitarias sin procesar, dinámicas sin procesar y dinámicas mejoradas con Orbit\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore minexp, succ, fontsize, labelsize */}\n",
        "\n",
        "<span id=\"benchmark-qft+m-process-fidelity-with-orbit-a-qiskit-function-by-quantum-elements\" />\n",
        "\n",
        "# Evalúa la fidelidad del proceso QFT+M con Orbit, una función de Qiskit desarrollada por Quantum Elements\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "warning",
      "metadata": {},
      "source": [
        "*Tiempo estimado de ejecución:* 2 minutos en un procesador Heron r3. (NOTA: Se trata únicamente de una estimación. (El tiempo de ejecución puede variar.) De forma predeterminada, este tutorial envía tres trabajos de la función Orbit a una carga de trabajo en modo por lotes del Servicio de Cálculo de IBM Quantum, con 300 PUB por trabajo, lo que supone un total de 900 PUB y 921 600 tomas.\n",
        "\n",
        "*Advertencia:* Los circuitos dinámicos son, por el momento, una función experimental y están sujetos a limitaciones en Quantum Compute [\\[3\\]](#references) que podrían provocar fallos en los trabajos. Por ejemplo, el error 6073 indica que un trabajo ha superado el límite de memoria del hardware de control clásico [\\[4\\]](#references). Este cuaderno reduce ese riesgo al distribuir el tamaño de los circuitos entre tres tareas de computación cuántica en un mismo lote [\\[5\\]](#references). Cada comparación de tamaño fijo se mantiene en un único trabajo, mientras que los tamaños grandes y pequeños se emparejan para equilibrar las cargas de trabajo de control clásico de los trabajos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "learning",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados del aprendizaje\n",
        "\n",
        "* Prepara los estados del producto $\\mathrm{QFT}^\\dagger|x\\rangle$ utilizados por el estimador de fidelidad del proceso por muestreo que aparece en la figura 2a de la ref. [\\[1\\]](#references).\n",
        "* Elabora implementaciones unitarias y dinámicas equivalentes de la transformada de Fourier cuántica seguida de una medición (QFT+M).\n",
        "* Selecciona los qubits físicos para los circuitos dinámicos utilizando los datos actuales de calibración y conectividad.\n",
        "* Compara las estimaciones de fidelidad del proceso de la QFT+M —unitaria bruta, dinámica bruta y dinámica mejorada con Orbit— a medida que aumenta el tamaño del circuito.\n",
        "* Utiliza la API de transpilación optimizada de Orbit con `mode=\"raw\"` y `transpilation_mode=\"validate\"`.\n",
        "* Envía varias cargas de trabajo de Orbit a través de la API de modo por lotes, manteniendo cada comparación de tres estrategias de tamaño fijo en un único trabajo.\n",
        "* Revisa los metadatos de Orbit para confirmar si se han aplicado el desacoplamiento dinámico (DD) y la mitigación de errores de medición (MEM).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "La figura 2a de la ref. [\\[1\\]](#references) compara la fidelidad del proceso del canal ideal QFT+M con la de implementaciones unitarias y dinámicas con ruido. Para una etiqueta de base computacional muestreada $x$, la prueba de rendimiento prepara $\\mathrm{QFT}^\\dagger|x\\rangle$, aplica la implementación ruidosa de QFT+M y estima la probabilidad $p_x$ de obtener el resultado ideal correspondiente. Estos estados de la teoría cuántica de campos inversa son separables y pueden prepararse de forma eficiente mediante puertas de Hadamard y rotaciones de fase virtuales.\n",
        "\n",
        "Para e $m$ es etiquetas muestreadas de forma independiente, el cuaderno utiliza el estimador insesgado derivado en la ref. [\\[1\\]](#references) :\n",
        "\n",
        "$$\n",
        "\\widehat{\\mathcal{F}}_{\\mathrm{proc}} = \\frac{m}{m-1}\\left(\\frac{1}{m}\\sum_{\\ell=1}^{m}\\sqrt{p_{x_\\ell}}\\right)^2 - \\frac{1}{m(m-1)}\\sum_{\\ell=1}^{m}p_{x_\\ell}.\n",
        "$$\n",
        "\n",
        "La construcción dinámica sustituye las puertas de fase controlada de la QFT+M unitaria por mediciones a mitad del circuito y rotaciones de fase condicionadas clásicamente [\\[1\\]](#references). Mediante la medición diferida, ambos circuitos presentan la misma distribución de salida ideal. La forma dinámica elimina el requisito de la puerta de dos qubits «todos con todos» y, en su lugar, utiliza mediciones intermedias de « $O(n)$ » con propagación hacia adelante y sin restricciones de conectividad. La medición y la retroalimentación directa también provocan largos periodos de inactividad en los qubits que aún no se han medido, lo que hace que el DD sea especialmente relevante.\n",
        "\n",
        "**Relación con la figura 2a.** Este cuaderno sigue el protocolo de fidelidad al proceso presentado en el artículo, pero se trata de una adaptación didáctica centrada en Orbit, más que de una reproducción. Por ejemplo, mientras que en la figura 2a se utilizó `ibm_kyiv` un dispositivo con 2000 disparos, nosotros utilizamos un dispositivo `ibm_aachen` moderno con un recuento menor, de 1024 disparos, para ahorrar tiempo de la QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5ae4122d",
      "metadata": {},
      "source": [
        "<span id=\"example-results\" />\n",
        "\n",
        "## Resultados de ejemplo\n",
        "\n",
        "El gráfico estático que se muestra a continuación presenta las curvas medias de fidelidad del proceso obtenidas a partir de tres trabajos de desarrollo consecutivos ejecutados con `ibm_aachen` el proceso que se describe a continuación. Como se ha demostrado aquí, Orbit puede aumentar considerablemente la calidad de los circuitos dinámicos; la QFT dinámica alcanza la calidad de los puntos de referencia publicados y muestra una mejora con respecto a la QFT unitaria estándar. Como veremos, estos resultados se deben a una selección adecuada y automatizada de los qubits, a la inserción automática de desacoplamiento dinámico (sin optimización manual para este problema) y a la mitigación de los errores de medición. Por curiosidad, no te olvides de comparar estos resultados con los tuyos al final, sobre todo si eliges un backend diferente.\n",
        "\n",
        "**Nota:** Estos resultados son un ejemplo ilustrativo de una ejecución anterior satisfactoria con Orbit, no una garantía de rendimiento. Los resultados que se muestran a continuación deberían ser similares en términos generales, pero los detalles concretos dependen del dispositivo elegido y de sus propiedades, especialmente de los errores de medición y de reposo durante la ejecución.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1bb67ce9",
      "metadata": {},
      "source": [
        "![Fidelidad del proceso QFT en «ibm\\_aachen»](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/quantum-elements-orbit/dynamic_qft_orbit_tutorial_aachen_notebook_3job_average.svg)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Instala las versiones más recientes de los siguientes paquetes antes de seguir este tutorial:\n",
        "\n",
        "* `numpy`\n",
        "* `matplotlib`\n",
        "* `qiskit`\n",
        "* `qiskit-ibm-runtime`\n",
        "* `qiskit-ibm-catalog`\n",
        "\n",
        "```bash\n",
        "pip install qiskit qiskit-ibm-runtime qiskit-ibm-catalog numpy matplotlib\n",
        "```\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-md",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuración\n",
        "\n",
        "Inicia sesión en [IBM Quantum® Platform](), carga `ibm_aachen`y abre Quantum Elements Orbit desde [Qiskit Functions Catalog](/functions). El barrido predeterminado evalúa 15 tamaños de circuito, 20 cadenas de bits muestreadas por tamaño y tres estrategias. `NUM_BATCH_JOBS=3` distribuye los tamaños entre tres trabajos en un mismo [lote](/docs/guides/run-jobs-batch). Reducir `N_VALUES` o `M`, o aumentar `NUM_BATCH_JOBS`, si una tarea individual de circuito dinámico sigue alcanzando el límite de memoria de control clásico del backend. Reducir `SHOTS` cuando el objetivo sea reducir el consumo de recursos de ejecución, en lugar del número o la complejidad de los circuitos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "setup-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "qiskit_runtime_service._discover_account:WARNING:2026-07-21 15:57:39,310: Loading account with the given token. A saved account will not be used.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'backend': 'ibm_aachen',\n",
              " 'num_qubits': 156,\n",
              " 'n_values': [2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40],\n",
              " 'm': 20,\n",
              " 'shots': 1024,\n",
              " 'num_function_jobs': 3,\n",
              " 'n_groups': [[40, 2, 7, 15, 10], [35, 3, 6, 20, 9], [30, 4, 5, 25, 8]],\n",
              " 'pubs_per_job': [300, 300, 300],\n",
              " 'total_pubs': 900,\n",
              " 'total_shots': 921600}"
            ]
          },
          "execution_count": 15,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import warnings\n",
        "from collections import Counter, defaultdict\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "from qiskit import (\n",
        "    ClassicalRegister,\n",
        "    QuantumCircuit,\n",
        "    QuantumRegister,\n",
        "    transpile,\n",
        ")\n",
        "from qiskit.circuit import IfElseOp\n",
        "from qiskit.synthesis.qft import synth_qft_full\n",
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "from qiskit_ibm_runtime import Batch, QiskitRuntimeService\n",
        "\n",
        "IBM_BACKEND_NAME = \"ibm_aachen\"\n",
        "\n",
        "N_VALUES = [2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40]\n",
        "M = 20\n",
        "SHOTS = 1024\n",
        "RNG_SEED = 12345\n",
        "OPTIMIZATION_LEVEL = 0\n",
        "NUM_BATCH_JOBS = 3\n",
        "STRATEGY_LABELS = (\"unitary/raw\", \"dynamic/raw\", \"dynamic/orbit\")\n",
        "\n",
        "\n",
        "def balanced_n_groups(\n",
        "    n_values: list[int], num_jobs: int = 3\n",
        ") -> list[list[int]]:\n",
        "    values = sorted(n_values)\n",
        "    if len(set(values)) != len(values):\n",
        "        raise ValueError(\"N_VALUES must not contain duplicates\")\n",
        "    if not 1 <= num_jobs <= len(values):\n",
        "        raise ValueError(\"NUM_BATCH_JOBS must be between 1 and len(N_VALUES)\")\n",
        "\n",
        "    max_group_size = (len(values) + num_jobs - 1) // num_jobs\n",
        "    groups = [[] for _ in range(num_jobs)]\n",
        "    loads = [0] * num_jobs\n",
        "    pair_counts = [0] * num_jobs\n",
        "    remaining = values.copy()\n",
        "\n",
        "    while len(remaining) >= 2:\n",
        "        candidates = [\n",
        "            i\n",
        "            for i, group in enumerate(groups)\n",
        "            if len(group) + 2 <= max_group_size\n",
        "        ]\n",
        "        if not candidates:\n",
        "            break\n",
        "        smallest = remaining.pop(0)\n",
        "        largest = remaining.pop()\n",
        "        job_index = min(\n",
        "            candidates, key=lambda i: (loads[i], len(groups[i]), i)\n",
        "        )\n",
        "        pair = (\n",
        "            [largest, smallest]\n",
        "            if pair_counts[job_index] % 2 == 0\n",
        "            else [smallest, largest]\n",
        "        )\n",
        "        groups[job_index].extend(pair)\n",
        "        loads[job_index] += smallest + largest\n",
        "        pair_counts[job_index] += 1\n",
        "\n",
        "    while remaining:\n",
        "        value = remaining.pop()\n",
        "        candidates = [\n",
        "            i for i, group in enumerate(groups) if len(group) < max_group_size\n",
        "        ]\n",
        "        job_index = min(\n",
        "            candidates, key=lambda i: (loads[i], len(groups[i]), i)\n",
        "        )\n",
        "        groups[job_index].append(value)\n",
        "        loads[job_index] += value\n",
        "\n",
        "    return groups\n",
        "\n",
        "\n",
        "N_GROUPS = balanced_n_groups(N_VALUES, NUM_BATCH_JOBS)\n",
        "\n",
        "service = QiskitRuntimeService(channel=\"ibm_quantum_platform\")\n",
        "backend = service.backend(IBM_BACKEND_NAME)\n",
        "if \"if_else\" not in backend.target.operation_names:\n",
        "    backend.target.add_instruction(IfElseOp, name=\"if_else\")\n",
        "\n",
        "catalog = QiskitFunctionsCatalog(channel=\"ibm_quantum_platform\")\n",
        "quantum_elements_orbit = catalog.load(\"quantum-elements/orbit\")\n",
        "if quantum_elements_orbit is None:\n",
        "    raise RuntimeError(\n",
        "        \"Quantum Elements Orbit is not enabled for this IBM Quantum instance.\"\n",
        "    )\n",
        "\n",
        "required_qubits = max(N_VALUES)\n",
        "if backend.num_qubits < required_qubits:\n",
        "    raise ValueError(\n",
        "        f\"Backend {backend.name} has {backend.num_qubits} qubits, \"\n",
        "        f\"but this benchmark needs at least {required_qubits}.\"\n",
        "    )\n",
        "\n",
        "{\n",
        "    \"backend\": backend.name,\n",
        "    \"num_qubits\": backend.num_qubits,\n",
        "    \"n_values\": N_VALUES,\n",
        "    \"m\": M,\n",
        "    \"shots\": SHOTS,\n",
        "    \"num_function_jobs\": NUM_BATCH_JOBS,\n",
        "    \"n_groups\": N_GROUPS,\n",
        "    \"pubs_per_job\": [\n",
        "        len(group) * M * len(STRATEGY_LABELS) for group in N_GROUPS\n",
        "    ],\n",
        "    \"total_pubs\": len(N_VALUES) * M * len(STRATEGY_LABELS),\n",
        "    \"total_shots\": len(N_VALUES) * M * len(STRATEGY_LABELS) * SHOTS,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "build-md",
      "metadata": {},
      "source": [
        "<span id=\"build-qft+m-circuits\" />\n",
        "\n",
        "## Construir circuitos de QFT+M\n",
        "\n",
        "Para cada entero muestreado $x$, `bit_inv_qft` se prepara el estado de producto $\\mathrm{QFT}^\\dagger|x\\rangle$ mediante operaciones de Hadamard seguidas de rotaciones de fase. A continuación, el cuaderno añade bien la QFT unitaria estándar, bien su equivalente semiclásico dinámico QFT+M.\n",
        "\n",
        "Ambas implementaciones omiten la red de intercambio final. Por lo tanto, el orden clásico de visualización de bits en Qiskit hace que la cadena medida esperada sea la inversa de la representación binaria rellenada con ceros de « $x$ », que se codifica mediante `format(x, f\"0{n}b\")[::-1]`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "circuits-code",
      "metadata": {},
      "outputs": [],
      "source": [
        "def bit_inv_qft(circuit: QuantumCircuit, x: int, conv: str = \"LSB\") -> None:\n",
        "    num_qubits = circuit.num_qubits\n",
        "    circuit.h(range(num_qubits))\n",
        "    for j in range(num_qubits):\n",
        "        phase = (\n",
        "            2 * np.pi * x / 2 ** (num_qubits - j)\n",
        "            if conv == \"LSB\"\n",
        "            else 2 * np.pi * x / 2 ** (j + 1)\n",
        "        )\n",
        "        circuit.p(-phase, j)\n",
        "\n",
        "\n",
        "def build_unitary_qft_circuit(num_qubits: int, x: int) -> QuantumCircuit:\n",
        "    if not 0 <= x < 2**num_qubits:\n",
        "        raise ValueError(\n",
        "            f\"x={x} is outside the {num_qubits}-qubit basis range\"\n",
        "        )\n",
        "    qreg = QuantumRegister(num_qubits, \"q\")\n",
        "    creg = ClassicalRegister(num_qubits, \"c\")\n",
        "    circuit = QuantumCircuit(qreg, creg, name=f\"unitary_qft_{num_qubits}q\")\n",
        "    bit_inv_qft(circuit, x)\n",
        "    circuit.append(\n",
        "        synth_qft_full(num_qubits, do_swaps=False), range(num_qubits)\n",
        "    )\n",
        "    circuit.measure(range(num_qubits), range(num_qubits))\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def _warn_if_precision_loss(max_num_entanglements: int) -> None:\n",
        "    if max_num_entanglements > -np.finfo(float).minexp:\n",
        "        warnings.warn(\n",
        "            \"precision loss in QFT.\"\n",
        "            f\" The rotation needed to represent {max_num_entanglements} entanglements\"\n",
        "            \" is smaller than the smallest normal floating-point number.\",\n",
        "            category=RuntimeWarning,\n",
        "            stacklevel=4,\n",
        "        )\n",
        "\n",
        "\n",
        "def synth_dynamic_qft(\n",
        "    circuit: QuantumCircuit, *, do_swaps: bool = False\n",
        ") -> QuantumCircuit:\n",
        "    num_qubits = circuit.num_qubits\n",
        "    creg = circuit.cregs[0]\n",
        "    _warn_if_precision_loss(num_qubits - 1)\n",
        "\n",
        "    for j in reversed(range(num_qubits)):\n",
        "        circuit.h(j)\n",
        "        circuit.measure([j], [j])\n",
        "\n",
        "        if j > 0:\n",
        "            with circuit.if_test((creg[j], 1)):\n",
        "                for k in reversed(range(j)):\n",
        "                    circuit.p(np.pi * (2.0 ** (k - j)), k)\n",
        "\n",
        "    if do_swaps:\n",
        "        for i in range(num_qubits // 2):\n",
        "            circuit.swap(i, num_qubits - i - 1)\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def build_dynamic_qft_circuit(num_qubits: int, x: int) -> QuantumCircuit:\n",
        "    if not 0 <= x < 2**num_qubits:\n",
        "        raise ValueError(\n",
        "            f\"x={x} is outside the {num_qubits}-qubit basis range\"\n",
        "        )\n",
        "    qreg = QuantumRegister(num_qubits, \"q\")\n",
        "    creg = ClassicalRegister(num_qubits, \"c\")\n",
        "    circuit = QuantumCircuit(qreg, creg, name=f\"dynamic_qft_{num_qubits}q\")\n",
        "    bit_inv_qft(circuit, x)\n",
        "    synth_dynamic_qft(circuit, do_swaps=False)\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def target_output_bitstring(x: int, n_qubits: int) -> str:\n",
        "    return format(int(x), f\"0{n_qubits}b\")[::-1]\n",
        "\n",
        "\n",
        "def process_fidelity_from_success_probabilities(\n",
        "    success_probabilities: list[float],\n",
        ") -> float:\n",
        "    m = len(success_probabilities)\n",
        "    if m <= 1:\n",
        "        raise ValueError(\n",
        "            \"m must be larger than 1 for the process-fidelity estimator\"\n",
        "        )\n",
        "    succ = np.asarray(success_probabilities, dtype=float)\n",
        "    return float(\n",
        "        (m / (m - 1)) * (np.mean(np.sqrt(succ)) ** 2)\n",
        "        - np.sum(succ) / (m * (m - 1))\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "select-md",
      "metadata": {},
      "source": [
        "<span id=\"select-dynamic-circuit-physical-qubits\" />\n",
        "\n",
        "## Seleccionar qubits físicos de circuito dinámico\n",
        "\n",
        "La implementación dinámica no requiere puertas de dos qubits, por lo que sus qubits físicos no tienen por qué formar un subgrafo conectado. Para cada tamaño de circuito, el selector clasifica los qubits del backend actuales utilizando una puntuación ponderada en un 80 % hacia un menor error de lectura y en un 10 % hacia cada uno de los siguientes criterios: mayor « $T_1$ » y mayor « $T_2$ ». En primer lugar, elige los qubits con mayor puntuación que no tengan acoplamientos directos entre sí, siempre que sea posible, lo que puede reducir la exposición a la diafonía entre vecinos más cercanos; a continuación, rellena las posiciones restantes según la puntuación.\n",
        "\n",
        "Las `dynamic/raw` variantes y `dynamic/orbit` utilizan exactamente el mismo diseño seleccionado para un tamaño determinado, por lo que su comparación depende del diseño. En cambio, el transpilador se encarga de mapear y enrutar el `unitary/raw` circuito, ya que este requiere conectividad de dos qubits. Esta selección basada en la calibración en tiempo de ejecución es específica de este tutorial; no se trata de la disposición fija de 40 qubits `ibm_kyiv` utilizada para los experimentos de la figura 2a del artículo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "select-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The top 3 qubits (according to our scoring): \n",
            "[{'qubit': 0, 't1': 0.0002514242577986401, 't2': 0.00037559012475638467, 'measurement_error': 0.0028076171875, 'score': 1.0}, {'qubit': 20, 't1': 0.0002526252407383437, 't2': 0.00038493543771861573, 'measurement_error': 0.00390625, 'score': 1.0}, {'qubit': 25, 't1': 0.0002712841332005567, 't2': 0.00025793268824583597, 'measurement_error': 0.0040283203125, 'score': 1.0}]\n",
            "Worst  3 qubits (according to our scoring): \n",
            "[{'qubit': 146, 't1': 7.619772882181663e-05, 't2': 0.00014166983578724752, 'measurement_error': 0.0802001953125, 'score': 0.05893378230453208}, {'qubit': 51, 't1': 0.00014200221819602618, 't2': 1.930870507157441e-06, 'measurement_error': 0.054443359375, 'score': 0.04600110909801309}, {'qubit': 35, 't1': 7.116087485472031e-05, 't2': 9.463696242928626e-05, 'measurement_error': 0.14501953125, 'score': 0.03289891864200328}]\n"
          ]
        }
      ],
      "source": [
        "def value_from_property(raw):\n",
        "    if raw is None:\n",
        "        return None\n",
        "    if isinstance(raw, tuple):\n",
        "        return raw[0]\n",
        "    return getattr(raw, \"value\", raw)\n",
        "\n",
        "\n",
        "def qubit_property_value(properties, qubit: int, *names: str) -> float | None:\n",
        "    for name in names:\n",
        "        try:\n",
        "            value = value_from_property(\n",
        "                properties.qubit_property(qubit, name)\n",
        "            )\n",
        "        except Exception:\n",
        "            value = None\n",
        "        if value is not None:\n",
        "            return float(value)\n",
        "    return None\n",
        "\n",
        "\n",
        "def measurement_error(properties, qubit: int) -> float | None:\n",
        "    readout = qubit_property_value(properties, qubit, \"readout_error\")\n",
        "    if readout is not None:\n",
        "        return readout\n",
        "    p01 = qubit_property_value(properties, qubit, \"prob_meas0_prep1\")\n",
        "    p10 = qubit_property_value(properties, qubit, \"prob_meas1_prep0\")\n",
        "    if p01 is not None and p10 is not None:\n",
        "        return 0.5 * (p01 + p10)\n",
        "    return None\n",
        "\n",
        "\n",
        "def coupling_edges(backend) -> list[tuple[int, int]]:\n",
        "    coupling_map = getattr(backend, \"coupling_map\", None)\n",
        "    if coupling_map is not None:\n",
        "        try:\n",
        "            return [(int(a), int(b)) for a, b in coupling_map.get_edges()]\n",
        "        except Exception:\n",
        "            pass\n",
        "    built = backend.target.build_coupling_map()\n",
        "    return [(int(a), int(b)) for a, b in built.get_edges()]\n",
        "\n",
        "\n",
        "def neighbor_map(backend) -> dict[int, set[int]]:\n",
        "    neighbors = {qubit: set() for qubit in range(backend.num_qubits)}\n",
        "    for a, b in coupling_edges(backend):\n",
        "        neighbors[a].add(b)\n",
        "        neighbors[b].add(a)\n",
        "    return neighbors\n",
        "\n",
        "\n",
        "def anchored_score(\n",
        "    value: float | None, *, good: float, bad: float, higher_is_better: bool\n",
        ") -> float:\n",
        "    if value is None:\n",
        "        return 0.0\n",
        "    if higher_is_better:\n",
        "        low, high = sorted((bad, good))\n",
        "        score = (value - low) / (high - low)\n",
        "    else:\n",
        "        low, high = sorted((good, bad))\n",
        "        score = (high - value) / (high - low)\n",
        "    return float(min(1.0, max(0.0, score)))\n",
        "\n",
        "\n",
        "def qubit_metrics(backend) -> list[dict]:\n",
        "    properties = backend.properties()\n",
        "    rows = []\n",
        "    for qubit in range(backend.num_qubits):\n",
        "        t1 = qubit_property_value(properties, qubit, \"T1\", \"t1\")\n",
        "        t2 = qubit_property_value(properties, qubit, \"T2\", \"t2\")\n",
        "        meas_error = measurement_error(properties, qubit)\n",
        "        measurement_score = anchored_score(\n",
        "            meas_error, good=0.005, bad=0.05, higher_is_better=False\n",
        "        )\n",
        "        t1_score = anchored_score(\n",
        "            t1, good=0.00025, bad=0.00005, higher_is_better=True\n",
        "        )\n",
        "        t2_score = anchored_score(\n",
        "            t2, good=0.00025, bad=0.00005, higher_is_better=True\n",
        "        )\n",
        "        rows.append(\n",
        "            {\n",
        "                \"qubit\": qubit,\n",
        "                \"t1\": t1,\n",
        "                \"t2\": t2,\n",
        "                \"measurement_error\": meas_error,\n",
        "                \"score\": 0.8 * measurement_score\n",
        "                + 0.1 * t1_score\n",
        "                + 0.1 * t2_score,\n",
        "            }\n",
        "        )\n",
        "    return sorted(rows, key=lambda row: row[\"score\"], reverse=True)\n",
        "\n",
        "\n",
        "def select_dynamic_qubits(backend, n_qubits: int) -> list[int]:\n",
        "    ranked = qubit_metrics(backend)\n",
        "    neighbors = neighbor_map(backend)\n",
        "    selected = []\n",
        "    blocked = set()\n",
        "    for row in ranked:\n",
        "        qubit = row[\"qubit\"]\n",
        "        if qubit in blocked:\n",
        "            continue\n",
        "        selected.append(qubit)\n",
        "        blocked.add(qubit)\n",
        "        blocked.update(neighbors.get(qubit, set()))\n",
        "        if len(selected) == n_qubits:\n",
        "            return selected\n",
        "\n",
        "    for row in ranked:\n",
        "        qubit = row[\"qubit\"]\n",
        "        if qubit not in selected:\n",
        "            selected.append(qubit)\n",
        "        if len(selected) == n_qubits:\n",
        "            return selected\n",
        "    raise RuntimeError(f\"Could not select {n_qubits} physical qubits\")\n",
        "\n",
        "\n",
        "print(\"The top 3 qubits (according to our scoring): \")\n",
        "print(qubit_metrics(backend)[0:3])\n",
        "print(\"Worst  3 qubits (according to our scoring): \")\n",
        "print(qubit_metrics(backend)[-3:])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "prepare-md",
      "metadata": {},
      "source": [
        "<span id=\"prepare-the-benchmark-pubs\" />\n",
        "\n",
        "## Prepara los PUB de referencia\n",
        "\n",
        "Para cada `(N, x)` par, el cuaderno compila primero los circuitos lógicos y crea un «Sampler» PUB para cada estrategia:\n",
        "\n",
        "* `unitary/raw`: QFT+M unitaria en la disposición seleccionada por el transpilador, con el enrutamiento necesario y sin Orbit DD ni MEM.\n",
        "* `dynamic/raw`: QFT+M dinámica en los qubits físicos seleccionados mediante calibración, sin Orbit DD ni MEM.\n",
        "* `dynamic/orbit`: el mismo circuito dinámico transpilado en los mismos qubits físicos, con Orbit DD y MEM activados.\n",
        "\n",
        "El « PUB » mejorado con Orbit utiliza `transpilation_mode=\"validate\"` porque su asignación ya ha sido seleccionada. Orbit valida el circuito físico proporcionado en lugar de reasignarlo y, a continuación, aplica sus canalizaciones DD y MEM. Dado que solo la curva dinámica mejorada requiere MEM, no debe interpretarse como una comparación aislada entre el DD y la ausencia de DD.\n",
        "\n",
        "Los PUB, las opciones «per- PUB » y los registros de resultados se almacenan por índice de trabajo por lotes. `unitary/raw`Para cada $N$ fijo, los PUB, `dynamic/raw`, y `dynamic/orbit` se mantienen juntos en el mismo trabajo. El asistente de agrupación empareja circuitos de gran y pequeño tamaño, alterna su orden y equilibra la suma de « $N$ » entre los tres trabajos, como un indicador sencillo de la carga de trabajo del control clásico.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "prepare-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'num_function_jobs': 3,\n",
              " 'n_groups': {0: [40, 2, 7, 15, 10],\n",
              "  1: [35, 3, 6, 20, 9],\n",
              "  2: [30, 4, 5, 25, 8]},\n",
              " 'n_load_per_job': {0: 74, 1: 73, 2: 72},\n",
              " 'pubs_per_job': {0: 300, 1: 300, 2: 300},\n",
              " 'expected_executions_per_job': {0: 307200, 1: 307200, 2: 307200},\n",
              " 'first_pub_record_by_job': {0: {'job_index': 0,\n",
              "   'n_qubits': 40,\n",
              "   'target_decimal': 853235401719,\n",
              "   'target_bitstring': '1110111111000000110100110001010101100011',\n",
              "   'label': 'unitary/raw',\n",
              "   'pub_options': {'mode': 'raw'},\n",
              "   'dynamic_qubits': None,\n",
              "   'transpiled_depth': 4778,\n",
              "   'transpiled_size': 28259},\n",
              "  1: {'job_index': 1,\n",
              "   'n_qubits': 35,\n",
              "   'target_decimal': 26888951661,\n",
              "   'target_bitstring': '10110110111010110010110101000010011',\n",
              "   'label': 'unitary/raw',\n",
              "   'pub_options': {'mode': 'raw'},\n",
              "   'dynamic_qubits': None,\n",
              "   'transpiled_depth': 3614,\n",
              "   'transpiled_size': 21204},\n",
              "  2: {'job_index': 2,\n",
              "   'n_qubits': 30,\n",
              "   'target_decimal': 620442965,\n",
              "   'target_bitstring': '101010101010110011011111001001',\n",
              "   'label': 'unitary/raw',\n",
              "   'pub_options': {'mode': 'raw'},\n",
              "   'dynamic_qubits': None,\n",
              "   'transpiled_depth': 2835,\n",
              "   'transpiled_size': 15078}},\n",
              " 'largest_dynamic_qubit_set': [0,\n",
              "  20,\n",
              "  25,\n",
              "  27,\n",
              "  33,\n",
              "  59,\n",
              "  74,\n",
              "  80,\n",
              "  95,\n",
              "  144,\n",
              "  151,\n",
              "  155,\n",
              "  79,\n",
              "  90,\n",
              "  60,\n",
              "  68,\n",
              "  114,\n",
              "  107,\n",
              "  13,\n",
              "  126,\n",
              "  133,\n",
              "  103,\n",
              "  3,\n",
              "  87,\n",
              "  53,\n",
              "  41,\n",
              "  130,\n",
              "  5,\n",
              "  98,\n",
              "  135,\n",
              "  153,\n",
              "  15,\n",
              "  116,\n",
              "  45,\n",
              "  7,\n",
              "  48,\n",
              "  136,\n",
              "  11,\n",
              "  147,\n",
              "  77]}"
            ]
          },
          "execution_count": 31,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "strategy_options = {\n",
        "    \"unitary/raw\": {\"mode\": \"raw\"},\n",
        "    \"dynamic/raw\": {\"mode\": \"raw\"},\n",
        "    \"dynamic/orbit\": {\"mode\": \"orbit\", \"transpilation_mode\": \"validate\"},\n",
        "}\n",
        "rng = np.random.default_rng(RNG_SEED)\n",
        "pubs_by_job = [[] for _ in N_GROUPS]\n",
        "pub_options_by_job = [[] for _ in N_GROUPS]\n",
        "pub_records_by_job = [[] for _ in N_GROUPS]\n",
        "layout_summary = {}\n",
        "\n",
        "target_decimals_by_n = {\n",
        "    n_qubits: [int(x) for x in rng.integers(0, 2**n_qubits, size=M)]\n",
        "    for n_qubits in N_VALUES\n",
        "}\n",
        "\n",
        "for job_index, n_group in enumerate(N_GROUPS):\n",
        "    for n_qubits in n_group:\n",
        "        dynamic_qubits = select_dynamic_qubits(backend, n_qubits)\n",
        "        layout_summary[str(n_qubits)] = {\"dynamic_qubits\": dynamic_qubits}\n",
        "\n",
        "        for x in target_decimals_by_n[n_qubits]:\n",
        "            target_bitstring = target_output_bitstring(x, n_qubits)\n",
        "            unitary_logical = build_unitary_qft_circuit(n_qubits, x)\n",
        "            dynamic_logical = build_dynamic_qft_circuit(n_qubits, x)\n",
        "\n",
        "            unitary_transpiled = transpile(\n",
        "                unitary_logical,\n",
        "                backend=backend,\n",
        "                optimization_level=OPTIMIZATION_LEVEL,\n",
        "                seed_transpiler=RNG_SEED,\n",
        "            )\n",
        "            dynamic_transpiled = transpile(\n",
        "                dynamic_logical,\n",
        "                backend=backend,\n",
        "                optimization_level=OPTIMIZATION_LEVEL,\n",
        "                seed_transpiler=RNG_SEED,\n",
        "                initial_layout=dynamic_qubits,\n",
        "            )\n",
        "\n",
        "            circuits_by_label = {\n",
        "                \"unitary/raw\": unitary_transpiled,\n",
        "                \"dynamic/raw\": dynamic_transpiled,\n",
        "                \"dynamic/orbit\": dynamic_transpiled,\n",
        "            }\n",
        "            for label in STRATEGY_LABELS:\n",
        "                circuit = circuits_by_label[label]\n",
        "                options = dict(strategy_options[label])\n",
        "                pubs_by_job[job_index].append((circuit, None, SHOTS))\n",
        "                pub_options_by_job[job_index].append(options)\n",
        "                pub_records_by_job[job_index].append(\n",
        "                    {\n",
        "                        \"job_index\": job_index,\n",
        "                        \"n_qubits\": n_qubits,\n",
        "                        \"target_decimal\": x,\n",
        "                        \"target_bitstring\": target_bitstring,\n",
        "                        \"label\": label,\n",
        "                        \"pub_options\": options,\n",
        "                        \"dynamic_qubits\": (\n",
        "                            dynamic_qubits\n",
        "                            if label.startswith(\"dynamic/\")\n",
        "                            else None\n",
        "                        ),\n",
        "                        \"transpiled_depth\": circuit.depth(),\n",
        "                        \"transpiled_size\": circuit.size(),\n",
        "                    }\n",
        "                )\n",
        "\n",
        "{\n",
        "    \"num_function_jobs\": len(N_GROUPS),\n",
        "    \"n_groups\": {\n",
        "        job_index: group for job_index, group in enumerate(N_GROUPS)\n",
        "    },\n",
        "    \"n_load_per_job\": {\n",
        "        job_index: sum(group) for job_index, group in enumerate(N_GROUPS)\n",
        "    },\n",
        "    \"pubs_per_job\": {\n",
        "        job_index: len(pubs) for job_index, pubs in enumerate(pubs_by_job)\n",
        "    },\n",
        "    \"expected_executions_per_job\": {\n",
        "        job_index: len(pubs) * SHOTS\n",
        "        for job_index, pubs in enumerate(pubs_by_job)\n",
        "    },\n",
        "    \"first_pub_record_by_job\": {\n",
        "        job_index: records[0]\n",
        "        for job_index, records in enumerate(pub_records_by_job)\n",
        "    },\n",
        "    \"largest_dynamic_qubit_set\": layout_summary[str(max(N_VALUES))][\n",
        "        \"dynamic_qubits\"\n",
        "    ],\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "run-md",
      "metadata": {},
      "source": [
        "<span id=\"run-the-benchmark\" />\n",
        "\n",
        "## Ejecuta la prueba de rendimiento\n",
        "\n",
        "Crea un [lote](/docs/guides/run-jobs-batch) y, a continuación, envía tres trabajos de la función Orbit a ese lote.\n",
        "\n",
        "Los trabajos se dividen en grupos según el número de qubits, de modo que se puedan comparar las tres estrategias para un « $N$ » fijo. Es decir, los 60 PUB para un « $N$ » fijo —20 entradas muestreadas multiplicadas por las tres estrategias— se ejecutan, por tanto, en el mismo trabajo y pueden compararse así de la forma más equitativa posible (de lo contrario, si se ejecutaran en trabajos diferentes, el dispositivo podría desviarse mientras está en la cola). Los grupos predeterminados combinan circuitos grandes y pequeños y contienen 300 PUB cada uno, lo que reduce la probabilidad de que un trabajo acumule todos los programas dinámicos más grandes, al tiempo que se mantienen las comparaciones dentro de cada trabajo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "run-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'runtime_batch_id': '80120e36-436d-46c7-96c9-597ec86060c1',\n",
              " 'jobs': {0: {'backend': 'ibm_aachen',\n",
              "   'function_job_id': '2b6b05ac-0136-40f0-94bf-ade5658f5f4f',\n",
              "   'status': 'QUEUED',\n",
              "   'n_values': [40, 2, 7, 15, 10],\n",
              "   'num_pubs': 300},\n",
              "  1: {'backend': 'ibm_aachen',\n",
              "   'function_job_id': '4f046fd4-80e4-460b-87c7-e7252691f764',\n",
              "   'status': 'QUEUED',\n",
              "   'n_values': [35, 3, 6, 20, 9],\n",
              "   'num_pubs': 300},\n",
              "  2: {'backend': 'ibm_aachen',\n",
              "   'function_job_id': '6d70d64d-fa38-4ca2-9cbd-ffda5d8c99be',\n",
              "   'status': 'QUEUED',\n",
              "   'n_values': [30, 4, 5, 25, 8],\n",
              "   'num_pubs': 300}}}"
            ]
          },
          "execution_count": 32,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "runtime_batch = Batch(backend=backend)\n",
        "jobs = []\n",
        "try:\n",
        "    for job_index, pubs in enumerate(pubs_by_job):\n",
        "        jobs.append(\n",
        "            quantum_elements_orbit.run(\n",
        "                primitive=\"sampler\",\n",
        "                pubs=pubs,\n",
        "                backend_name=backend.name,\n",
        "                options={\n",
        "                    \"pub_options\": pub_options_by_job[job_index],\n",
        "                    \"save_backend_info\": True,\n",
        "                },\n",
        "            )\n",
        "        )\n",
        "except Exception:\n",
        "    runtime_batch.close()\n",
        "    raise\n",
        "\n",
        "{\n",
        "    \"runtime_batch_id\": runtime_batch.session_id,\n",
        "    \"jobs\": {\n",
        "        job_index: {\n",
        "            \"backend\": backend.name,\n",
        "            \"function_job_id\": job.job_id,\n",
        "            \"status\": job.status(),\n",
        "            \"n_values\": N_GROUPS[job_index],\n",
        "            \"num_pubs\": len(pubs_by_job[job_index]),\n",
        "        }\n",
        "        for job_index, job in enumerate(jobs)\n",
        "    },\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "results-md",
      "metadata": {},
      "source": [
        "<span id=\"retrieve-results-and-compute-process-fidelity\" />\n",
        "\n",
        "## Recuperar los resultados y calcular la fidelidad del proceso\n",
        "\n",
        "Recupera y valida cada resultado de grupo de qubits de forma independiente y, a continuación, fusiona las tres secuencias de tareas mediante sus registros indexados por tarea. El lote permanece abierto mientras se solicitan todos los resultados de las funciones y se cierra en un `finally` bloque una vez que se ha intentado ejecutar cada trabajo. Para cada PUB, $p_x$ es la probabilidad asignada a la cadena de bits esperada. `dynamic/orbit``extract_counts` lee los recuentos devueltos al llamante; en este caso, se trata de los recuentos ajustados según MEM cuando la mitigación tiene éxito. `extract_raw_counts` además, recupera los recuentos sin mitigar correspondientes registrados en los metadatos de Orbit. El código agrupa los 20 valores de « $p_x$ » para cada `(N, label)` par y aplica el estimador presentado anteriormente.\n",
        "\n",
        "Por lo tanto, el diccionario representado `process_fidelity` en el gráfico utiliza recuentos sin ajustar para `unitary/raw` y `dynamic/raw`, pero recuentos ajustados mediante MEM para `dynamic/orbit`. El diccionario paralelo `raw_process_fidelity` conserva un cálculo sin modificaciones para cada estrategia y resulta útil a la hora de aislar el efecto de MEM del resto del proceso de Orbit. MEM corrige el histograma de salida devuelto; no puede modificar con carácter retroactivo el resultado de una medición realizada a mitad del circuito que ya haya sido utilizado por el control anticipativo en tiempo real.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "results-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'runtime_batch_id': '80120e36-436d-46c7-96c9-597ec86060c1',\n",
              " 'function_job_ids': {0: '2b6b05ac-0136-40f0-94bf-ade5658f5f4f',\n",
              "  1: '4f046fd4-80e4-460b-87c7-e7252691f764',\n",
              "  2: '6d70d64d-fa38-4ca2-9cbd-ffda5d8c99be'},\n",
              " 'n_groups': {0: [40, 2, 7, 15, 10],\n",
              "  1: [35, 3, 6, 20, 9],\n",
              "  2: [30, 4, 5, 25, 8]},\n",
              " 'process_fidelity': {'2': {'dynamic/orbit': 0.9870551835473073,\n",
              "   'dynamic/raw': 0.9912537998030566,\n",
              "   'unitary/raw': 0.9884650767434809},\n",
              "  '3': {'dynamic/orbit': 0.9662998634131841,\n",
              "   'dynamic/raw': 0.9699631603283018,\n",
              "   'unitary/raw': 0.9388637172865901},\n",
              "  '4': {'dynamic/orbit': 0.9271266520750502,\n",
              "   'dynamic/raw': 0.7334377020091254,\n",
              "   'unitary/raw': 0.9010122207121433},\n",
              "  '5': {'dynamic/orbit': 0.8883501513887149,\n",
              "   'dynamic/raw': 0.6577660260669806,\n",
              "   'unitary/raw': 0.7806443417987445},\n",
              "  '6': {'dynamic/orbit': 0.8524225652033044,\n",
              "   'dynamic/raw': 0.4444025126308521,\n",
              "   'unitary/raw': 0.7167426842521228},\n",
              "  '7': {'dynamic/orbit': 0.832962085697061,\n",
              "   'dynamic/raw': 0.2253787798698553,\n",
              "   'unitary/raw': 0.5746335601063436},\n",
              "  '8': {'dynamic/orbit': 0.7881895956180588,\n",
              "   'dynamic/raw': 0.16909516699831612,\n",
              "   'unitary/raw': 0.5408263851227074},\n",
              "  '9': {'dynamic/orbit': 0.7422635627368794,\n",
              "   'dynamic/raw': 0.0242474245097341,\n",
              "   'unitary/raw': 0.4855953298367578},\n",
              "  '10': {'dynamic/orbit': 0.7002274273149545,\n",
              "   'dynamic/raw': 0.033718865729016285,\n",
              "   'unitary/raw': 0.3607634828181049},\n",
              "  '15': {'dynamic/orbit': 0.4694995355699914,\n",
              "   'dynamic/raw': 7.70970394736842e-05,\n",
              "   'unitary/raw': 0.054582117352985286},\n",
              "  '20': {'dynamic/orbit': 0.24118032284867608,\n",
              "   'dynamic/raw': 4.235164736271502e-22,\n",
              "   'unitary/raw': 0.0},\n",
              "  '25': {'dynamic/orbit': 0.027122712989729438,\n",
              "   'dynamic/raw': 0.0,\n",
              "   'unitary/raw': 0.0},\n",
              "  '30': {'dynamic/orbit': 0.0003581886014704875,\n",
              "   'dynamic/raw': 0.0,\n",
              "   'unitary/raw': 0.0},\n",
              "  '35': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},\n",
              "  '40': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0}},\n",
              " 'mean_success_probability': {'2': {'dynamic/orbit': 0.987060546875,\n",
              "   'dynamic/raw': 0.991259765625,\n",
              "   'unitary/raw': 0.9884765625},\n",
              "  '3': {'dynamic/orbit': 0.96630859375,\n",
              "   'dynamic/raw': 0.969970703125,\n",
              "   'unitary/raw': 0.939013671875},\n",
              "  '4': {'dynamic/orbit': 0.9271484375,\n",
              "   'dynamic/raw': 0.7337890625,\n",
              "   'unitary/raw': 0.901318359375},\n",
              "  '5': {'dynamic/orbit': 0.88837890625,\n",
              "   'dynamic/raw': 0.657861328125,\n",
              "   'unitary/raw': 0.78115234375},\n",
              "  '6': {'dynamic/orbit': 0.85244140625,\n",
              "   'dynamic/raw': 0.44453125,\n",
              "   'unitary/raw': 0.71728515625},\n",
              "  '7': {'dynamic/orbit': 0.8330078125,\n",
              "   'dynamic/raw': 0.22568359375,\n",
              "   'unitary/raw': 0.575390625},\n",
              "  '8': {'dynamic/orbit': 0.788232421875,\n",
              "   'dynamic/raw': 0.169189453125,\n",
              "   'unitary/raw': 0.541796875},\n",
              "  '9': {'dynamic/orbit': 0.742333984375,\n",
              "   'dynamic/raw': 0.0244140625,\n",
              "   'unitary/raw': 0.487353515625},\n",
              "  '10': {'dynamic/orbit': 0.70029296875,\n",
              "   'dynamic/raw': 0.033935546875,\n",
              "   'unitary/raw': 0.363037109375},\n",
              "  '15': {'dynamic/orbit': 0.4697265625,\n",
              "   'dynamic/raw': 0.00029296875,\n",
              "   'unitary/raw': 0.055615234375},\n",
              "  '20': {'dynamic/orbit': 0.241357421875,\n",
              "   'dynamic/raw': 4.8828125e-05,\n",
              "   'unitary/raw': 0.0},\n",
              "  '25': {'dynamic/orbit': 0.041015625, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},\n",
              "  '30': {'dynamic/orbit': 0.0013671875,\n",
              "   'dynamic/raw': 0.0,\n",
              "   'unitary/raw': 0.0},\n",
              "  '35': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},\n",
              "  '40': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0}}}"
            ]
          },
          "execution_count": 35,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "def extract_counts(pub_result) -> dict[str, int]:\n",
        "    data = getattr(pub_result, \"data\", None)\n",
        "    if data is None:\n",
        "        raise TypeError(\"pub_result.data is missing\")\n",
        "\n",
        "    for name in dir(data):\n",
        "        if name.startswith(\"_\"):\n",
        "            continue\n",
        "        register = getattr(data, name)\n",
        "        get_counts = getattr(register, \"get_counts\", None)\n",
        "        if callable(get_counts):\n",
        "            counts = get_counts()\n",
        "            if counts:\n",
        "                return counts\n",
        "\n",
        "    raise TypeError(\n",
        "        \"No classical register with get_counts() found in pub_result.data\"\n",
        "    )\n",
        "\n",
        "\n",
        "def extract_raw_counts(pub_result) -> dict[str, int]:\n",
        "    orbit_metadata = pub_result.metadata.get(\"quantum_elements_orbit\", {})\n",
        "    mem_report = orbit_metadata.get(\"measurementErrorMitigation\", {})\n",
        "    return mem_report.get(\"rawCounts\") or extract_counts(pub_result)\n",
        "\n",
        "\n",
        "def probability_for_bitstring(\n",
        "    counts: dict[str, int], bitstring: str, n_qubits: int\n",
        ") -> float:\n",
        "    total = sum(counts.values())\n",
        "    if total <= 0:\n",
        "        return 0.0\n",
        "    normalized = Counter()\n",
        "    for measured, count in counts.items():\n",
        "        key = measured.replace(\" \", \"\")[-n_qubits:].zfill(n_qubits)\n",
        "        normalized[key] += count\n",
        "    return float(normalized.get(bitstring, 0) / total)\n",
        "\n",
        "\n",
        "results_by_job = {}\n",
        "job_failures = []\n",
        "try:\n",
        "    for job_index, job in enumerate(jobs):\n",
        "        try:\n",
        "            job_result = job.result()\n",
        "        except Exception as exc:\n",
        "            job_logs = getattr(job, \"logs\", lambda: \"\")()\n",
        "            if job_logs:\n",
        "                print(f\"Logs for job {job_index} ({job.job_id}):\\n{job_logs}\")\n",
        "            job_failures.append(\n",
        "                f\"job {job_index} ({job.job_id}) failed: {type(exc).__name__}: {exc}\"\n",
        "            )\n",
        "            continue\n",
        "\n",
        "        expected_results = len(pub_records_by_job[job_index])\n",
        "        if len(job_result) != expected_results:\n",
        "            job_failures.append(\n",
        "                f\"job {job_index} ({job.job_id}) returned {len(job_result)} PUB results; \"\n",
        "                f\"expected {expected_results}\"\n",
        "            )\n",
        "            continue\n",
        "        results_by_job[job_index] = job_result\n",
        "finally:\n",
        "    runtime_batch.close()\n",
        "\n",
        "if job_failures:\n",
        "    raise RuntimeError(\n",
        "        \"One or more batched Orbit jobs failed:\\n\" + \"\\n\".join(job_failures)\n",
        "    )\n",
        "\n",
        "grouped_success = defaultdict(list)\n",
        "grouped_raw_success = defaultdict(list)\n",
        "pub_summaries = []\n",
        "\n",
        "for job_index, job_result in sorted(results_by_job.items()):\n",
        "    records = pub_records_by_job[job_index]\n",
        "    for record, pub_result in zip(records, job_result, strict=True):\n",
        "        label = record[\"label\"]\n",
        "        n_qubits = record[\"n_qubits\"]\n",
        "        counts = extract_counts(pub_result)\n",
        "        raw_counts = extract_raw_counts(pub_result)\n",
        "        success = probability_for_bitstring(\n",
        "            counts, record[\"target_bitstring\"], n_qubits\n",
        "        )\n",
        "        raw_success = probability_for_bitstring(\n",
        "            raw_counts, record[\"target_bitstring\"], n_qubits\n",
        "        )\n",
        "        key = (n_qubits, label)\n",
        "        grouped_success[key].append(success)\n",
        "        grouped_raw_success[key].append(raw_success)\n",
        "\n",
        "        orbit_report = pub_result.metadata.get(\"quantum_elements_orbit\", {})\n",
        "        mem_report = orbit_report.get(\"measurementErrorMitigation\", {})\n",
        "        pub_summaries.append(\n",
        "            {\n",
        "                **record,\n",
        "                \"function_job_id\": jobs[job_index].job_id,\n",
        "                \"runtime_batch_id\": runtime_batch.session_id,\n",
        "                \"success_probability\": success,\n",
        "                \"raw_success_probability\": raw_success,\n",
        "                \"orbit_mode\": orbit_report.get(\"mode\"),\n",
        "                \"transpilation_mode\": orbit_report.get(\"transpilationMode\"),\n",
        "                \"physical_layout\": orbit_report.get(\"physicalLayout\"),\n",
        "                \"dd_status\": orbit_report.get(\"status\", \"not_applied\"),\n",
        "                \"num_sequences_added\": orbit_report.get(\n",
        "                    \"numSequencesAdded\", 0\n",
        "                ),\n",
        "                \"num_gaps_filled\": orbit_report.get(\"numGapsFilled\", 0),\n",
        "                \"dynamic_dd_seq\": orbit_report.get(\"dynamicDdSeq\"),\n",
        "                \"mem_status\": mem_report.get(\"status\", \"not_requested\"),\n",
        "                \"warnings\": orbit_report.get(\"warnings\", [])\n",
        "                + mem_report.get(\"warnings\", []),\n",
        "            }\n",
        "        )\n",
        "\n",
        "process_fidelity = defaultdict(dict)\n",
        "raw_process_fidelity = defaultdict(dict)\n",
        "mean_success_probability = defaultdict(dict)\n",
        "raw_mean_success_probability = defaultdict(dict)\n",
        "\n",
        "for (n_qubits, label), probabilities in sorted(grouped_success.items()):\n",
        "    n_key = str(n_qubits)\n",
        "    process_fidelity[n_key][label] = (\n",
        "        process_fidelity_from_success_probabilities(probabilities)\n",
        "    )\n",
        "    mean_success_probability[n_key][label] = float(np.mean(probabilities))\n",
        "\n",
        "for (n_qubits, label), probabilities in sorted(grouped_raw_success.items()):\n",
        "    n_key = str(n_qubits)\n",
        "    raw_process_fidelity[n_key][label] = (\n",
        "        process_fidelity_from_success_probabilities(probabilities)\n",
        "    )\n",
        "    raw_mean_success_probability[n_key][label] = float(np.mean(probabilities))\n",
        "\n",
        "process_fidelity = dict(process_fidelity)\n",
        "raw_process_fidelity = dict(raw_process_fidelity)\n",
        "mean_success_probability = dict(mean_success_probability)\n",
        "raw_mean_success_probability = dict(raw_mean_success_probability)\n",
        "\n",
        "{\n",
        "    \"runtime_batch_id\": runtime_batch.session_id,\n",
        "    \"function_job_ids\": {\n",
        "        job_index: job.job_id for job_index, job in enumerate(jobs)\n",
        "    },\n",
        "    \"n_groups\": {\n",
        "        job_index: group for job_index, group in enumerate(N_GROUPS)\n",
        "    },\n",
        "    \"process_fidelity\": process_fidelity,\n",
        "    \"mean_success_probability\": mean_success_probability,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "metadata-md",
      "metadata": {},
      "source": [
        "<span id=\"inspect-orbit-dd-metadata\" />\n",
        "\n",
        "## Revisar los metadatos de Orbit DD\n",
        "\n",
        "El resumen que figura a continuación comprueba los `dynamic/orbit` metadatos de « PUB », en lugar de dar por hecho que se ha insertado el DD solicitado. Comprueba el estado, la secuencia DD dinámica indicada, las advertencias y el número de huecos rellenados y de secuencias añadidas. Una inserción correcta debería dar lugar a recuentos distintos de cero para al menos algunos PUB, aunque los valores exactos dependen del circuito programado, de las restricciones de sincronización del backend y del tamaño del circuito. Estos metadatos describen la secuencia aplicada por Orbit; no deben considerarse como el protocolo FC-DD del artículo, a menos que el informe establezca explícitamente dicha equivalencia.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "metadata-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'dynamic_orbit_dd_status_counts': {'40': {'dd_inserted': 20},\n",
              "  '2': {'dd_inserted': 20},\n",
              "  '7': {'dd_inserted': 20},\n",
              "  '15': {'dd_inserted': 20},\n",
              "  '10': {'dd_inserted': 20},\n",
              "  '35': {'dd_inserted': 20},\n",
              "  '3': {'dd_inserted': 20},\n",
              "  '6': {'dd_inserted': 20},\n",
              "  '20': {'dd_inserted': 20},\n",
              "  '9': {'dd_inserted': 20},\n",
              "  '30': {'dd_inserted': 20},\n",
              "  '4': {'dd_inserted': 20},\n",
              "  '5': {'dd_inserted': 20},\n",
              "  '25': {'dd_inserted': 20},\n",
              "  '8': {'dd_inserted': 20}},\n",
              " 'dynamic_orbit_sequences_added': {'40': 31200,\n",
              "  '2': 40,\n",
              "  '7': 840,\n",
              "  '15': 4200,\n",
              "  '10': 1800,\n",
              "  '35': 23800,\n",
              "  '3': 120,\n",
              "  '6': 600,\n",
              "  '20': 7600,\n",
              "  '9': 1440,\n",
              "  '30': 17400,\n",
              "  '4': 240,\n",
              "  '5': 400,\n",
              "  '25': 12000,\n",
              "  '8': 1120},\n",
              " 'warning_examples': [{'n_qubits': 40,\n",
              "   'target_decimal': 853235401719,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},\n",
              "  {'n_qubits': 40,\n",
              "   'target_decimal': 954673909846,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},\n",
              "  {'n_qubits': 40,\n",
              "   'target_decimal': 524641045908,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},\n",
              "  {'n_qubits': 40,\n",
              "   'target_decimal': 185651043478,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},\n",
              "  {'n_qubits': 40,\n",
              "   'target_decimal': 587114273567,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']}]}"
            ]
          },
          "execution_count": 36,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "dd_summary = defaultdict(lambda: Counter())\n",
        "sequence_totals = defaultdict(int)\n",
        "warning_examples = []\n",
        "\n",
        "for summary in pub_summaries:\n",
        "    if summary[\"label\"] != \"dynamic/orbit\":\n",
        "        continue\n",
        "    n_key = str(summary[\"n_qubits\"])\n",
        "    dd_summary[n_key][summary[\"dd_status\"]] += 1\n",
        "    sequence_totals[n_key] += int(summary.get(\"num_sequences_added\") or 0)\n",
        "    if summary.get(\"warnings\") and len(warning_examples) < 5:\n",
        "        warning_examples.append(\n",
        "            {\n",
        "                \"n_qubits\": summary[\"n_qubits\"],\n",
        "                \"target_decimal\": summary[\"target_decimal\"],\n",
        "                \"warnings\": summary[\"warnings\"],\n",
        "            }\n",
        "        )\n",
        "\n",
        "{\n",
        "    \"dynamic_orbit_dd_status_counts\": {\n",
        "        key: dict(value) for key, value in dd_summary.items()\n",
        "    },\n",
        "    \"dynamic_orbit_sequences_added\": dict(sequence_totals),\n",
        "    \"warning_examples\": warning_examples,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "plot-md",
      "metadata": {},
      "source": [
        "<span id=\"plot-process-fidelity-curves\" />\n",
        "\n",
        "## Trazar curvas de fidelidad del proceso\n",
        "\n",
        "El gráfico muestra la estimación puntual muestreada de la fidelidad del proceso QFT+M en función del número de qubits para las tres estrategias. `dynamic/raw` y `dynamic/orbit` comparten una disposición física para cada tamaño; `unitary/raw` utiliza la disposición y el enrutamiento del transpilador.\n",
        "\n",
        "A diferencia de la figura 2a, este gráfico no muestra una curva unitaria con DD ni bandas de incertidumbre, y sus curvas sin procesar no han sido corregidas para mitigar los efectos de la lectura. Lo mejor es interpretarlo como una comparación de escalado « Figure-2a-style » para este flujo de trabajo de Orbit, y no como una reproducción directa de las curvas publicadas.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 51,
      "id": "1972ceb9",
      "metadata": {},
      "outputs": [],
      "source": [
        "from datetime import datetime\n",
        "from zoneinfo import ZoneInfo\n",
        "\n",
        "closed_at = runtime_batch.details()[\"closed_at\"]  # \"2026-07-22T00:08:54.89Z\"\n",
        "closed_dt = datetime.fromisoformat(closed_at.replace(\"Z\", \"+00:00\"))\n",
        "closed_local = closed_dt.astimezone(ZoneInfo(\"America/Los_Angeles\"))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 53,
      "id": "plot-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/quantum-elements-orbit/extracted-outputs/plot-code-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "labels = [\"dynamic/orbit\", \"dynamic/raw\", \"unitary/raw\"]\n",
        "colors = {\n",
        "    \"dynamic/orbit\": \"#26735b\",\n",
        "    \"dynamic/raw\": \"#9b1c31\",\n",
        "    \"unitary/raw\": \"#6e6e6e\",\n",
        "}\n",
        "pretty_labels = {\n",
        "    \"dynamic/orbit\": \"Dynamic QFT+M with Orbit\",\n",
        "    \"dynamic/raw\": \"Dynamic QFT+M\",\n",
        "    \"unitary/raw\": \"Unitary QFT+M\",\n",
        "}\n",
        "\n",
        "series = []\n",
        "for label in labels:\n",
        "    values = [process_fidelity[str(n)][label] for n in N_VALUES]\n",
        "    log_values = [value if value > 0.0 else float(\"nan\") for value in values]\n",
        "    series.append((label, values, log_values))\n",
        "\n",
        "nonzero_values = [\n",
        "    value\n",
        "    for _, _, log_values in series\n",
        "    for value in log_values\n",
        "    if value > 0.0\n",
        "]\n",
        "if not nonzero_values:\n",
        "    raise RuntimeError(\n",
        "        \"No nonzero process-fidelity values found for log inset\"\n",
        "    )\n",
        "log_floor = min(nonzero_values) / 2\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(9.8, 5.6))\n",
        "for label, values, _ in series:\n",
        "    ax.plot(\n",
        "        N_VALUES,\n",
        "        values,\n",
        "        marker=\"o\",\n",
        "        linewidth=2.0,\n",
        "        markersize=5,\n",
        "        color=colors[label],\n",
        "        label=pretty_labels[label],\n",
        "    )\n",
        "\n",
        "ax.set_xlabel(\"N qubits\")\n",
        "ax.set_ylabel(\"Process fidelity\")\n",
        "finished_time_for_title = globals().get(\"finished_local\", closed_local)\n",
        "ax.set_title(\n",
        "    f\"Dynamic QFT Orbit results on {IBM_BACKEND_NAME}\\n\"\n",
        "    f\"Job finished {finished_time_for_title:%Y-%m-%d %H:%M %Z}\"\n",
        ")\n",
        "ax.set_xticks(N_VALUES)\n",
        "ax.set_ylim(bottom=0)\n",
        "ax.grid(axis=\"both\", alpha=0.25)\n",
        "ax.legend(loc=\"upper right\")\n",
        "\n",
        "inset = ax.inset_axes([0.53, 0.31, 0.44, 0.43])\n",
        "for label, _, log_values in series:\n",
        "    inset.plot(\n",
        "        N_VALUES,\n",
        "        log_values,\n",
        "        marker=\"o\",\n",
        "        linewidth=2.0,\n",
        "        markersize=5,\n",
        "        color=colors[label],\n",
        "    )\n",
        "inset.set_yscale(\"log\")\n",
        "inset.set_ylim(bottom=log_floor)\n",
        "inset.set_xlim(min(N_VALUES), max(N_VALUES))\n",
        "inset.set_title(\"Log scale; zeros omitted\", fontsize=9)\n",
        "inset.grid(axis=\"both\", alpha=0.25)\n",
        "inset.tick_params(axis=\"both\", labelsize=8)\n",
        "inset.patch.set_alpha(0.96)\n",
        "\n",
        "fig.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "refs",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referencias\n",
        "\n",
        "1. [E. Bäumer *et al.*, «Transformada de Fourier cuántica mediante circuitos dinámicos», arXiv:2403.09514; *Physical Review Letters* **133**, 150602 (2024)](https://arxiv.org/abs/2403.09514)\n",
        "\n",
        "2. [Introducción a « Qiskit Functions »](/docs/guides/functions)\n",
        "\n",
        "3. [Limitaciones de la computación cuántica para las variables de extensión](/docs/guides/stretch#qiskit-runtime-limitations)\n",
        "\n",
        "4. [IBM Quantum códigos de error: 6073](https://ibm.biz/error_codes#6073)\n",
        "\n",
        "5. [Ejecutar tareas por lotes](/docs/guides/run-jobs-batch)\n",
        "\n",
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximos pasos\n",
        "\n",
        "* Consulta la [guía de Orbit](/docs/guides/quantum-elements-orbit) y la documentación [de referencia de la API](/docs/api/functions/quantum-elements-orbit).\n",
        "* Prueba con un backend diferente, un diseño alternativo o experimenta con secuencias alternativas de desacoplamiento dinámico habilitadas para órbitas modificando la `dd_strategy` opción. Ten en cuenta que, debido al carácter experimental de los circuitos dinámicos, debes prestar atención a los posibles modos de fallo del sistema (véanse [\\[3\\]](#references) y [\\[4\\]](#references) ). Si observas valores de estiramiento negativos [\\[3\\]](#references), prueba con una secuencia DD más corta (con menos pulsos). Si te encuentras con [\\[4\\]](#references), aumenta `NUM_BATCH_JOBS`, reduce `M`, o reduce los valores más altos de `N_VALUES`.\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": 120
  },
  "nbformat": 4,
  "nbformat_minor": 5
}