{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Implementar y ejecutar una plantilla de Qiskit Function para AQC + dinámica del hamiltoniano de Trotter\"\n",
        "description: \"Implementa la plantilla de la función de dinámica hamiltoniana AQC + Trotter en Qiskit Serverless y, a continuación, ejecútala en un simulador y en una QPU.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore Trotter Trotterization quimb cotengra cotengrust Suzuki fidelities isa */}\n",
        "\n",
        "<span id=\"deploy-and-run-a-qiskit-function-template-for-aqc-+-trotter-hamiltonian-dynamics\" />\n",
        "\n",
        "# Implementar y ejecutar una plantilla de Qiskit Function para AQC + dinámica del hamiltoniano de Trotter\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "version-info",
      "metadata": {
        "tags": [
          "version-info"
        ]
      },
      "source": [
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "overview",
      "metadata": {},
      "source": [
        "<span id=\"overview\" />\n",
        "\n",
        "## Visión general\n",
        "\n",
        "Esta es una plantilla de función de Qiskit independiente del experimento para la dinámica hamiltoniana. Dado un hamiltoniano de Pauli de vecinos más cercanos de tipo « 1D », un estado inicial preparado (opcional) y un conjunto de observables, el programa ejecuta la evolución temporal de Trotter, la compresión de circuitos mediante compilación cuántica aproximada (AQC) y la ejecución mitigada, y a continuación devuelve las series temporales de cada observable. Si se intercambian la configuración (PRE) y el análisis (POST), el mismo núcleo da lugar a un experimento diferente:\n",
        "\n",
        "| PRE (tu configuración)                                                                       | FUNCIÓN (implementada aquí)                                                                                                   | PUBLICA (tu análisis)                                                                                                      |\n",
        "| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |\n",
        "| Prepara un estado, ya sea un circuito o un estado de producto, con un impulso local opcional | Síntesis de Trotter → Compresión AQC → ejecución en `statevector`, `fake`, o `runtime`, devolviendo un $\\langle O \\rangle(t)$ | $S(q, \\omega)$ para la dispersión de neutrones, o la magnetización, el transporte, la dinámica de enfriamiento rápido, etc |\n",
        "\n",
        "La plantilla está publicada en el [repositorio de plantillas de funciones de Qiskit](https://github.com/qiskit-community/qiskit-function-templates/tree/main/physics/aqc_trotter), junto con el resto de plantillas de aplicaciones. Este cuaderno lo implementa en tu propia cuenta de Qiskit Serverless. Ejecútalo una vez y, a partir de ahí, cualquier cuaderno podrá llamar a la función con `serverless.load(\"aqc-dynamics-function\")`.\n",
        "\n",
        "Para ver un ejemplo científico práctico, consulta [«Simulación de la dispersión de neutrones con un flujo de trabajo sin servidor basado en AQC y dinámica de Trotter»](/docs/tutorials/simulate-neutron-scattering-with-a-serverless-workflow), que utiliza esta función para calcular el factor de estructura dinámico de KCuF$_3$. Este cuaderno trata, en cambio, sobre la implementación y el contrato de entrada.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar, asegúrate de que dispones de lo siguiente en el entorno del kernel de este portátil:\n",
        "\n",
        "* Qiskit SDK v2.0 o posterior (`pip install qiskit`).\n",
        "* El cliente «Qiskit IBM Catalog» (`pip install qiskit-ibm-catalog`), que implementa y ejecuta cargas de trabajo en Qiskit Serverless.\n",
        "\n",
        "No es necesario instalar localmente las dependencias científicas propias de la función (`qiskit-addon-aqc-tensor`, `cotengrust`, `qiskit-aer`).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "source-files",
      "metadata": {},
      "source": [
        "<span id=\"get-the-template-source-files\" />\n",
        "\n",
        "## Descarga los archivos fuente de la plantilla\n",
        "\n",
        "La función es un pequeño paquete Python que Qiskit Serverless se ejecuta en la nube, por lo que su código fuente debe existir en forma de archivos locales que se suben en el momento de la implementación. El paquete está publicado en el repositorio de plantillas de funciones de Qiskit.\n",
        "\n",
        "Descargar **[`source_files`](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fqiskit-community%2Fqiskit-function-templates%2Ftree%2Fmain%2Fphysics%2Faqc_trotter%2Fsource_files)**\n",
        "\n",
        "La descarga es un único archivo zip, cuyo nombre corresponde a la ruta completa del directorio en el repositorio:\n",
        "\n",
        "`qiskit-community qiskit-function-templates main physics aqc_trotter source_files.zip`\n",
        "\n",
        "1. Descomprímelo en el directorio donde se encuentra este cuaderno.\n",
        "2. Cambia el nombre de la carpeta extraída, que tiene ese nombre tan largo, por `source_files`.\n",
        "\n",
        "Tu directorio de trabajo quedará entonces así:\n",
        "\n",
        "```\n",
        "your-working-directory/\n",
        "├── function-template-aqc-trotter.ipynb    <- this notebook\n",
        "└── source_files/                         <- the renamed folder\n",
        "    ├── __init__.py\n",
        "    ├── program.py\n",
        "    └── source/\n",
        "        ├── __init__.py\n",
        "        ├── _serverless.py\n",
        "        ├── app_function.py\n",
        "        ├── aqc.py\n",
        "        ├── build.py\n",
        "        ├── execute.py\n",
        "        └── hamiltonian.py\n",
        "```\n",
        "\n",
        "El nombre tiene que ser exactamente ese `source_files`, porque así es como se suben los archivos en el paso `working_dir` 3.\n",
        "\n",
        "`program.py` es el punto de entrada que invoca la pasarela. Todo lo que aparece a continuación corresponde `source/` a la implementación, dividida por fases: síntesis hamiltoniana y de Trotter, compresión AQC y ejecución. No es necesario modificar nada para ejecutar los ejemplos que vienen a continuación. El paso 3 sube todo el directorio, así que repite ese paso cada vez que modifiques un archivo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "auth-md",
      "metadata": {},
      "source": [
        "<span id=\"1-authentication\" />\n",
        "\n",
        "## 1. Autenticación\n",
        "\n",
        "Utiliza `qiskit-ibm-catalog` para autenticarte en `QiskitServerless` con tu clave de API (token) y CRN (instancia), que puedes encontrar en el panel de control de [IBM Quantum® Platform](). Con estas credenciales, puedes crear una instancia del cliente «serverless» de forma local para cargar o ejecutar la función seleccionada:\n",
        "\n",
        "```python\n",
        "from qiskit_ibm_catalog import QiskitServerless\n",
        "serverless = QiskitServerless(channel=\"ibm_quantum_platform\", token=\"MY_TOKEN\", instance=\"MY_CRN\")\n",
        "```\n",
        "\n",
        "Si lo deseas, puedes utilizar `save_account()` para guardar tus credenciales en tu entorno local (consulta la guía [«Configurar tu cuenta de IBM Cloud® »](/docs/guides/cloud-setup#cloud-save) ). Ten en cuenta que esto guarda tus credenciales en el mismo archivo que [`QiskitRuntimeService.save_account()`](/docs/api/qiskit-ibm-runtime/qiskit-runtime-service#save_account):\n",
        "\n",
        "```python\n",
        "QiskitServerless.save_account(channel=\"ibm_quantum_platform\", token=\"MY_TOKEN\", instance=\"MY_CRN\")\n",
        "```\n",
        "\n",
        "Si la cuenta está guardada, no es necesario introducir el token para autenticarse:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 76,
      "id": "auth-code",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_catalog import QiskitServerless\n",
        "\n",
        "# Authenticate to the remote cluster\n",
        "# In this case, loading a saved account\n",
        "serverless = QiskitServerless()\n",
        "\n",
        "# REPLACE WITH YOUR OWN CREDENTIALS or SAVED ACCOUNT\n",
        "# serverless = QiskitServerless(channel=\"ibm_quantum_platform\", token=\"MY_TOKEN\", instance=\"MY_CRN\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "deps-md",
      "metadata": {},
      "source": [
        "<span id=\"2-declare-dependencies\" />\n",
        "\n",
        "## 2. Declarar las dependencias\n",
        "\n",
        "Paquetes que necesita la función, además de la imagen base gestionada de «serverless».\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  La puerta de enlace solo instala los nombres que figuran en su lista de permitidos ([`requirements-dynamic-dependencies.txt`](https://github.com/Qiskit/qiskit-serverless/blob/main/docker-images/requirements-dynamic-dependencies.txt)), que se identifican por el nombre del paquete y se asocian a la versión permitida mediante `==`. Cualquier otro elemento debe llegar de forma transitiva (como dependencia de un paquete incluido en la lista de permitidos). Se respeta la `[extras]` sintaxis: es `qiskit-addon-aqc-tensor[quimb-jax]` lo que instala `quimb` y `jax`. `cotengrust` Es necesario para optimizar el uso de la memoria durante la simulación de redes tensoriales. `qiskit-aer` se indica por separado para el backend `fake` (simulación local con ruido).\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "deps-code",
      "metadata": {},
      "outputs": [],
      "source": [
        "DEPENDENCIES = [\n",
        "    \"qiskit-addon-aqc-tensor[quimb-jax]==0.3.1\",\n",
        "    \"qiskit-aer==0.17.2\",\n",
        "    \"cotengrust==0.2.0\",\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "upload-md",
      "metadata": {},
      "source": [
        "<span id=\"3-define-and-upload-the-function\" />\n",
        "\n",
        "## 3. Define y sube la función\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "upload-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "QiskitFunction(aqc-dynamics-function)"
            ]
          },
          "execution_count": 119,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit_ibm_catalog import QiskitFunction\n",
        "\n",
        "fn = QiskitFunction(\n",
        "    title=\"aqc-dynamics-function\",\n",
        "    entrypoint=\"program.py\",\n",
        "    working_dir=\"source_files/\",\n",
        "    dependencies=DEPENDENCIES,\n",
        ")\n",
        "serverless.upload(fn)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "verify-md",
      "metadata": {},
      "source": [
        "<span id=\"4-verify-it-registered\" />\n",
        "\n",
        "## 4. Comprueba que esté registrado\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "verify-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "QiskitFunction(aqc-dynamics-function)"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "next(p for p in serverless.list() if p.title == \"aqc-dynamics-function\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "reference-md",
      "metadata": {},
      "source": [
        "<span id=\"function-reference\" />\n",
        "\n",
        "## referencia de funciones\n",
        "\n",
        "Esta es una breve introducción. Todos los campos se describen detalladamente en el archivo [README de la plantilla de AQC Dynamics](https://github.com/qiskit-community/qiskit-function-templates/blob/main/physics/aqc_trotter/README.md) : la tabla completa de entradas con sus reglas de validación, los campos de salida, los backends de ejecución y otros ejemplos prácticos. A continuación se ofrece una versión resumida, suficiente para entender los ejemplos que vienen a continuación.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "inputs-md",
      "metadata": {},
      "source": [
        "<span id=\"inputs\" />\n",
        "\n",
        "### Entradas\n",
        "\n",
        "Cada ejecución es una única `fn.run(...)` llamada. Solo son obligatorios los tres primeros datos de la tabla: `hamiltonian`, `t_steps`, y `aqc_segments`. Todo lo que viene después es opcional y, en su ausencia, se utiliza el valor por defecto indicado; por lo tanto, una llamada mínima requiere tres argumentos, y el resto de la tabla recoge las funcionalidades que puedes activar si lo deseas. El hamiltoniano determina `num_qubits` la longitud de la cadena, por lo que no hay que introducir ningún valor de tamaño por separado.\n",
        "\n",
        "| Entrada              | Valor predeterminado        | Descripción                                                                                                                                                           |                                                                                       |\n",
        "| -------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n",
        "| `hamiltonian`        | obligatorio                 | 1D Hamiltoniano de Pauli de «vecino más cercano» como un `SparsePauliOp`. Las cuerdas son operadores de Pauli, por lo que no hay ningún factor implícito de la mitad. |                                                                                       |\n",
        "| `t_steps`            | obligatorio                 | Total de pasos de Trotter. Evoluciona a e `T = t_steps * dt` informa de cada observable en cada `t_k = k * dt`.                                                       |                                                                                       |\n",
        "| `aqc_segments`       | obligatorio                 | Plan de compresión: una lista de `{\"n_steps\": k, \"ansatz_steps\": m}`. `sum(n_steps)` Los pasos están comprimidos; el resto se ejecuta como Trotter normal.            |                                                                                       |\n",
        "| `dt`                 | `0.2`                       | El tiempo físico ha avanzado un paso de Trotter.                                                                                                                      |                                                                                       |\n",
        "| `initial_state`      | \\`                          | 0...0>\\`                                                                                                                                                              | Un preparado `QuantumCircuit` para evolucionar. Añade un toque local a este circuito. |\n",
        "| `observables`        | por sitio `Z`               | Cualquier cosa se `EstimatorV2` acepta como argumento `observables` . Una observación por columna de salida.                                                          |                                                                                       |\n",
        "| `trotter_options`    | Suzuki de segundo orden     | `{\"method\": ..., \"synthesis_settings\": {...}}`. y `reps` `time` pertenecen a la función.                                                                              |                                                                                       |\n",
        "| `aqc_options`        | Vea la descripción          | `max_bond` (`32`), `cutoff` (`1e-8`), `autodiff_backend` (`\"jax\"`), `fidelity_target` (`None`), `optimizer_settings` (L-BFGS-B, `jac=True`, `maxiter=300`).           |                                                                                       |\n",
        "| `estimator_options`  | DD, giros, TREX             | `EstimatorV2.options`, aprobado tal cual. Un diccionario proporcionado sustituye por completo los valores predeterminados, en lugar de fusionarse con ellos.          |                                                                                       |\n",
        "| `transpiler_options` | `{\"optimization_level\": 3}` | `generate_preset_pass_manager` argumentos de palabra clave. `backend` y `target` se rechazan, ya que pertenecen a la ruta de ejecución.                               |                                                                                       |\n",
        "| `backend`            | `\"runtime\"`                 | `\"statevector\"`, `\"fake\"`, o `\"runtime\"`.                                                                                                                             |                                                                                       |\n",
        "| `backend_name`       | menos concurrido            | IBM® nombre del backend para `runtime`, o un backend ficticio con nombre.                                                                                             |                                                                                       |\n",
        "| `batches`            | `1`                         | Divide los circuitos entre N tareas de ejecución. Un lote envía un único trabajo y no crea ninguna sesión.                                                            |                                                                                       |\n",
        "| `parallel_sim`       | `False`                     | Distribuye las rutas del simulador local entre todos los núcleos disponibles con Ray. No tiene ningún efecto sobre `runtime`.                                         |                                                                                       |\n",
        "| `return_circuits`    | `False`                     | Indica en el resultado los circuitos lógicos AQC + Trotter, junto con las series de observables.                                                                      |                                                                                       |\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "backends-md",
      "metadata": {},
      "source": [
        "<span id=\"execution-backends\" />\n",
        "\n",
        "### Mecanismos de ejecución\n",
        "\n",
        "Las tres rutas comparten el mismo código y la misma configuración de medidas de mitigación. Solo se diferencian en el recorrido de los circuitos.\n",
        "\n",
        "| `backend`                          | Qué es                                                    | Credenciales                                         | Notas                                                                                                                                   |\n",
        "| ---------------------------------- | --------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |\n",
        "| `\"statevector\"`                    | Exacta `StatevectorEstimator`                             | Solo para cuentas sin servidor                       | La ruta de referencia exacta. No hay tiempo de QPU.                                                                                     |\n",
        "| `\"fake\"`                           | Simulación local ruidosa en un backend ficticio de Qiskit | Solo para cuentas sin servidor                       | Un fiel reflejo del camino `runtime` moderado. Necesidades `qiskit-aer`. El valor predeterminado es el de 127 qubits `fake_sherbrooke`. |\n",
        "| `\"runtime\"` (Valor predeterminado) | La mitigación `EstimatorV2` frente a una QPU real         | Cuenta «serverless» y una instancia con acceso a QPU | `backend_name` opcional; si se omite, se selecciona el dispositivo con menos actividad.                                                 |\n",
        "\n",
        "Ambas rutas del simulador siguen llamando a la función desplegada, por lo que necesitan una cuenta de Serverless guardada, aunque no utilicen tiempo de QPU. Los dos ejemplos siguientes ejecutan la misma carga de trabajo primero `statevector` en y, a continuación, en `runtime`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "output-md",
      "metadata": {},
      "source": [
        "<span id=\"output\" />\n",
        "\n",
        "### Resultado\n",
        "\n",
        "`job.result()` devuelve un diccionario simple:\n",
        "\n",
        "```python\n",
        "{\n",
        "    \"times\": [...],                  # length t_steps + 1, t_k = k * dt (t=0 is the prepared state)\n",
        "    \"expectation_values\": [[...]],   # shape (n_times, n_observables)\n",
        "    \"observable_labels\": [...],      # for example: [\"Z_0\", \"ZZ_0_1\"]\n",
        "    \"metadata\": {\n",
        "        \"n\", \"t_steps\", \"dt\", \"tier\",\n",
        "        \"aqc_compressed_steps\": 5,   # total compressed steps (= sum of segment n_steps)\n",
        "        \"aqc_segments\": [            # per segment: the plan plus its own results\n",
        "            {\"n_steps\": 3, \"ansatz_steps\": 1, \"steps\": [1, 2, 3], \"n_params\": 133,\n",
        "             \"fidelities\": {\"1\": ..., \"2\": ..., \"3\": ...}},\n",
        "            {\"n_steps\": 2, \"ansatz_steps\": 2, \"steps\": [4, 5], \"n_params\": 245,\n",
        "             \"fidelities\": {\"4\": ..., \"5\": ...}},\n",
        "        ],\n",
        "        \"execution_backend\",\n",
        "        \"aqc_fidelities\": {\"1\": ..., \"2\": ...},  # flat per-step fidelity, all compressed steps\n",
        "        \"circuit_stats\": {           # per-step 2q depth and gate count, full Trotter vs AQC\n",
        "            \"1\": {\"full_trotter\": {\"depth_2q\": ..., \"num_2q_gates\": ...},\n",
        "                \"aqc_trotter\":  {\"depth_2q\": ..., \"num_2q_gates\": ...}},\n",
        "            \"2\": {...},\n",
        "        },\n",
        "        \"warnings\": [...],           # non-fatal notices; for example, a cotengrust fallback\n",
        "        \"resource_usage\": {          # per stage; QPU_TIME is the charged QPU time\n",
        "            \"RUNNING: OPTIMIZING_FOR_HARDWARE\": {\"CPU_TIME\": ...},\n",
        "            \"RUNNING: WAITING_FOR_QPU\": {\"CPU_TIME\": ...},\n",
        "            \"RUNNING: EXECUTING_QPU\": {\"QPU_TIME\": ...},\n",
        "        },\n",
        "    },\n",
        "    # present only when return_circuits=True\n",
        "    \"circuits\": [QuantumCircuit, ...],  # one per evolved step; circuits[i] is at times[i + 1]\n",
        "}\n",
        "```\n",
        "\n",
        "`aqc_fidelities` y `circuit_stats` son los dos que hay que leer primero: juntos te indican si la compresión se ha mantenido fiel al original y si realmente ha conservado la profundidad. En `runtime`, se indica `resource_usage` el tiempo de espera en la cola por separado del tiempo de QPU que se te cobra. Una entrada rechazada se detecta rápidamente como un ( `ServerlessError` código `4615`) estructurado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "sim-md",
      "metadata": {},
      "source": [
        "<span id=\"simulator-example\" />\n",
        "\n",
        "## Ejemplo de simulador\n",
        "\n",
        "Ejecuta primero la función en el backend `statevector` exacto. No consume tiempo de la QPU y valida la implementación de principio a fin. El modelo que nos ocupa es una cadena de Ising de campo transversal de ocho qubits, y `observables` se omite para que la función mida el valor predeterminado de « $Z$ » por sitio.\n",
        "\n",
        "El plan de compresión es el dato que conviene comprender. Cada segmento `{\"n_steps\": k, \"ansatz_steps\": m}` comprime los pasos consecutivos `k` de Trotter en un ansatz construido a partir de un objetivo de Trotter de `m`-pasos, y cualquier paso adicional se `sum(n_steps)` ejecuta como un método de Trotter estándar. Los pasos iniciales, con bajo nivel de entrelazamiento, se comprimen bien en un ansatz de una sola capa poco profundo; los pasos posteriores, con mayor nivel de entrelazamiento, requieren uno más profundo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "sim-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "job ID: ee1f3793-e995-427d-81d1-5924549beb38\n"
          ]
        }
      ],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "fn = serverless.load(\"aqc-dynamics-function\")\n",
        "\n",
        "n = 8\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", [i, i + 1], 1.0) for i in range(n - 1)]\n",
        "    + [(\"X\", [i], 0.8) for i in range(n)],\n",
        "    num_qubits=n,\n",
        ")\n",
        "\n",
        "job = fn.run(\n",
        "    t_steps=8,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 4,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 2,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    hamiltonian=H,\n",
        "    aqc_options={\"max_bond\": 32},\n",
        "    backend=\"statevector\",\n",
        ")\n",
        "print(\"job ID:\", job.job_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "follow-md",
      "metadata": {},
      "source": [
        "<span id=\"follow-the-run-and-read-the-result\" />\n",
        "\n",
        "### Sigue la ejecución y lee el resultado\n",
        "\n",
        "`status()` muestra tanto el ciclo de vida general del trabajo como los subestados de cada etapa que la función publica a medida que se ejecuta. Estos mismos pasos se aplican a la instalación del hardware que se describe más adelante en esta guía:\n",
        "\n",
        "`QUEUED -> INITIALIZING -> RUNNING: OPTIMIZING_FOR_HARDWARE -> RUNNING: WAITING_FOR_QPU -> RUNNING: EXECUTING_QPU -> RUNNING: POST_PROCESSING -> DONE`\n",
        "\n",
        "| Valor `status()`                   | Etapa                                                                    |\n",
        "| ---------------------------------- | ------------------------------------------------------------------------ |\n",
        "| `RUNNING: OPTIMIZING_FOR_HARDWARE` | preparación del estado, construcción de Trotter, compresión AQC          |\n",
        "| `RUNNING: WAITING_FOR_QPU`         | en cola en la QPU (`runtime` solo en el backend)                         |\n",
        "| `RUNNING: EXECUTING_QPU`           | circuitos en ejecución (los simuladores locales lo indican directamente) |\n",
        "| `RUNNING: POST_PROCESSING`         | creación del diccionario de resultados                                   |\n",
        "\n",
        "Los estados terminales son `DONE`, `ERROR`, y `CANCELED`. Esta ejecución `statevector` no tiene cola de QPU, por lo que se salta `RUNNING: WAITING_FOR_QPU`. Utiliza `job.logs()` en cualquier momento para ver los registros de cada etapa, incluida la fidelidad AQC alcanzada en cada paso.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "status-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(job.status())  # re-run until this reports DONE"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 113,
      "id": "result-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "observables: ['Z_0', 'Z_1', 'Z_2', 'Z_3', 'Z_4', 'Z_5', 'Z_6', 'Z_7']\n",
            "shape: (9, 8) -> (n_times, n_observables)\n",
            "first row (t = 0, the prepared state): [1. 1. 1. 1. 1. 1. 1. 1.]\n",
            "last row (t = t_steps * dt): [0.1442 0.2956 0.4686 0.4877 0.4869 0.4686 0.2963 0.1441]\n",
            "AQC fidelities: {'1': 1.0, '2': 1.0, '3': 1.0, '4': 1.0, '5': 1.0, '6': 0.9999}\n",
            "2q depth at the final step: 210 (full Trotter) -> 79 (AQC + Trotter)\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "\n",
        "result = job.result()\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "\n",
        "print(\"observables:\", result[\"observable_labels\"])\n",
        "print(\"shape:\", ev.shape, \"-> (n_times, n_observables)\")\n",
        "print(\"first row (t = 0, the prepared state):\", np.round(ev[0], 4))\n",
        "print(\"last row (t = t_steps * dt):\", np.round(ev[-1], 4))\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "# What the compression bought: 2-qubit depth at the final time step.\n",
        "stats = result[\"metadata\"][\"circuit_stats\"][\n",
        "    str(result[\"metadata\"][\"t_steps\"])\n",
        "]\n",
        "print(\n",
        "    \"2q depth at the final step:\",\n",
        "    stats[\"full_trotter\"][\"depth_2q\"],\n",
        "    \"(full Trotter) ->\",\n",
        "    stats[\"aqc_trotter\"][\"depth_2q\"],\n",
        "    \"(AQC + Trotter)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hw-md",
      "metadata": {},
      "source": [
        "<span id=\"hardware-example\" />\n",
        "\n",
        "## Ejemplo de hardware\n",
        "\n",
        "Una llamada a una función se transpila `backend=\"runtime\"` y se ejecuta en un procesador real de IBM Quantum, con las técnicas de mitigación de errores integradas en la función: desacoplamiento dinámico ( XY4 ), «gate twirling» y extinción de errores de lectura por «twirling» (TREX). `backend_name` selecciona el dispositivo; si se omite, la función elige el que esté menos ocupado.\n",
        "\n",
        "No hay ningún cambio en el código científico. Lo que difiere del ejemplo del simulador es la longitud de la cadena, el número de pasos de Trotter, el plan de compresión, el backend y los ajustes explícitos de mitigación que se tratan en la siguiente sección.\n",
        "\n",
        "<span id=\"sizing-the-job-for-the-control-hardware\" />\n",
        "\n",
        "### Determinación de las especificaciones del hardware de control\n",
        "\n",
        "`estimator_options` ¿Merece la pena configurar esa entrada de forma deliberada? El «gate twirling» crea circuitos aleatorios independientes `num_randomizations` para cada PUB, y toda la tarea —cada PUB con todas sus aleatorizaciones— debe caber en la memoria de instrucciones del sistema de control clásico de la QPU. La función tiene por defecto 1.000 aleatorizaciones, por lo que una evolución de 10 pasos envía 11 PUB de 1.000 circuitos cada una: aproximadamente 11.000 instancias de circuitos en un solo trabajo.\n",
        "\n",
        "Si se supera el límite establecido por el sistema de control, la tarea falla con [el error 6073](https://ibm.biz/error_codes#6073). [Los límites de los trabajos](/docs/guides/job-limits) establecen los umbrales y cómo contabilizarlos; el principal es de un 26.8 millón de instrucciones del sistema de control por qubit, que se aplica por trabajo y no por PUB. El desacoplamiento dinámico añade puertas que cuentan para ello.\n",
        "\n",
        "Hay dos parámetros que controlan el tamaño:\n",
        "\n",
        "* `estimator_options` establece el presupuesto para las tomas. El número total de intentos es `num_randomizations * shots_per_randomization`, así que puedes cambiar las aleatorizaciones por intentos por aleatorización, mantener las estadísticas y, aun así, reducir el tamaño del programa. La siguiente celda utiliza 100 aleatorizaciones de 200 disparos cada una, lo que supone 20 000 disparos por observable y aproximadamente una décima parte de las instancias del circuito que se generarían con los valores predeterminados. Consulta « [TwirlingOptions](/docs/api/qiskit-ibm-runtime/options-twirling-options) » y [las opciones del estimador](/docs/guides/estimator-options) para ver el conjunto completo de campos.\n",
        "* `batches` divide los PUB en ese mismo número de trabajos de ejecución independientes, que es precisamente la solución que sugiere el propio error 6073 y la razón por la que es importante la estructuración por trabajo. Esta configuración envía `batches=4` aproximadamente tres PUB por trabajo, en lugar de once a la vez, y los trabajos se envían juntos en un solo lote, de modo que el grupo se pone en cola una sola vez, en lugar de que cada trabajo se ponga en cola por separado.\n",
        "\n",
        "Recuerda que un valor proporcionado sustituye `estimator_options` por completo los valores predeterminados de la función, en lugar de fusionarse con ellos; por lo tanto, el desacoplamiento dinámico y TREX se vuelven a definir en la siguiente celda para mantenerlos activados.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "hw-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "job ID (save this to reconnect later): 7229a8bf-9f83-4785-8dd4-489844abc2d9\n"
          ]
        }
      ],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "fn = serverless.load(\"aqc-dynamics-function\")\n",
        "\n",
        "n = 10\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", [i, i + 1], 1.0) for i in range(n - 1)]\n",
        "    + [(\"X\", [i], 0.8) for i in range(n)],\n",
        "    num_qubits=n,\n",
        ")\n",
        "\n",
        "job = fn.run(\n",
        "    t_steps=10,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 3,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 3,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    hamiltonian=H,\n",
        "    aqc_options={\"max_bond\": 32},\n",
        "    backend=\"runtime\",\n",
        "    backend_name=\"ibm_marrakesh\",\n",
        "    # The function defaults to 1000 twirling randomizations, which was too large\n",
        "    # for this device. Total shots is num_randomizations *\n",
        "    # shots_per_randomization, so this is 20,000 shots per observable.\n",
        "    estimator_options={\n",
        "        \"dynamical_decoupling\": {\"enable\": True, \"sequence_type\": \"XY4\"},\n",
        "        \"twirling\": {\n",
        "            \"enable_gates\": True,\n",
        "            \"num_randomizations\": 100,\n",
        "            \"shots_per_randomization\": 200,\n",
        "        },\n",
        "        \"resilience\": {\"measure_mitigation\": True},\n",
        "    },\n",
        ")\n",
        "print(\"job ID (save this to reconnect later):\", job.job_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hw-reconnect-md",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Volver a conectarse a un trabajo de larga duración\">\n",
        "  Una ejecución en hardware no es rápida, y la mayor parte del tiempo se realiza de forma clásica, en lugar de en la QPU. La compresión AQC se ejecuta dentro de la función antes de que nada llegue a la QPU, y la cola de la QPU se encuentra por encima de ella. No es necesario mantener abierto este cuaderno ni el kernel mientras se está ejecutando.\n",
        "\n",
        "  Copia el ID del trabajo que aparece en la celda anterior y anótalo. Las tres casillas siguientes te permiten retomar la partida más tarde:\n",
        "\n",
        "  1. Vuelve a conectarte (solo es necesario en una nueva sesión del kernel): vuelve a ejecutar la celda [de autenticación](#1-authentication) para recrearla y `serverless`, a continuación, vuelve a generar el `job` identificador a partir del ID que hayas guardado. Omite esta celda si aún te encuentras en la sesión en la que realizaste el envío, ya que el identificador ya está activo.\n",
        "  2. Comprobar el estado: volver a ejecutar hasta que se muestre el mensaje `DONE`.\n",
        "  3. Obtén el resultado: ejecútalo solo cuando el estado sea `DONE`.\n",
        "\n",
        "  Pega tu ID guardado en el espacio reservado de la siguiente celda de reconexión.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "hw-reconnect",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Reconnect to a previously submitted job by its ID. Only needed in a NEW kernel\n",
        "# session; if you are still in the session where you submitted, the `job` handle\n",
        "# from the preceding cell is already live, so skip this cell. Replace the ID that follows with your own.\n",
        "job = serverless.get_job_by_id(\"<your job ID>\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 116,
      "id": "hw-status",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "# Re-run this until it reports DONE, then fetch the result in the following cell.\n",
        "print(job.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 118,
      "id": "hw-result-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "backend: runtime\n",
            "shape: (11, 10) -> (n_times, n_observables)\n",
            "last row (t = t_steps * dt): [0.1504 0.1361 0.218  0.2144 0.2275 0.1783 0.1749 0.1599 0.0915 0.0922]\n",
            "AQC fidelities: {'1': 1.0, '2': 1.0, '3': 1.0, '4': 1.0, '5': 0.9999, '6': 0.9999}\n",
            "2q depth at the final step: 342 (full Trotter) -> 171 (AQC + Trotter)\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "\n",
        "# Run this only once the preceding status cell reports DONE. result() blocks until\n",
        "# the job finishes, so calling it earlier just waits.\n",
        "result = job.result()\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "\n",
        "print(\"backend:\", result[\"metadata\"][\"execution_backend\"])\n",
        "print(\"shape:\", ev.shape, \"-> (n_times, n_observables)\")\n",
        "print(\"last row (t = t_steps * dt):\", np.round(ev[-1], 4))\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "# What the compression bought: 2-qubit depth at the final time step.\n",
        "stats = result[\"metadata\"][\"circuit_stats\"][\n",
        "    str(result[\"metadata\"][\"t_steps\"])\n",
        "]\n",
        "print(\n",
        "    \"2q depth at the final step:\",\n",
        "    stats[\"full_trotter\"][\"depth_2q\"],\n",
        "    \"(full Trotter) ->\",\n",
        "    stats[\"aqc_trotter\"][\"depth_2q\"],\n",
        "    \"(AQC + Trotter)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "nextsteps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximos pasos\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendaciones\">\n",
        "  * Sigue el ejemplo «[Simulación de la dispersión de neutrones con AQC + dinámica de Trotter](/docs/tutorials/simulate-neutron-scattering-with-a-serverless-workflow) : flujo de trabajo sin servidor», que es el ejemplo complementario que invoca esta función desplegada para calcular el factor de estructura dinámico de KCuF$_3$.\n",
        "  * Consulta la [plantilla de AQC Dynamics en GitHub](https://github.com/qiskit-community/qiskit-function-templates/blob/main/physics/aqc_trotter/) para ver el contrato completo de entradas y salidas, más ejemplos y los detalles de la referencia bibliográfica.\n",
        "  * Echa un vistazo al [repositorio de plantillas de Qiskit Function](https://github.com/qiskit-community/qiskit-function-templates/tree/main/physics/aqc_trotter) para encontrar otras plantillas de aplicaciones creadas de la misma manera.\n",
        "  * Lee la [guía de Qiskit Serverless sobre la gestión de funciones implementadas.](/docs/guides/serverless)\n",
        "  * Profundiza en la etapa de compresión AQC con el [complemento de Qiskit:](https://qiskit.github.io/qiskit-addon-aqc-tensor/) documentación de AQC-Tensor.\n",
        "</Admonition>\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"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}