{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "title",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Ejecuta cargas de trabajo cuánticas con QRMI\"\n",
        "description: \"Utiliza la interfaz de gestión de recursos cuánticos para gestionar las cargas de trabajo de « IBM Quantum » y ejecutar un flujo de trabajo de química cuántica desde un entorno de HPC.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore QRMI SPANK GRES Slurm LUCJ CCSD pvdz hcore Pellegrini rustup cregs CUDA SBATCH dotenv */}\n",
        "\n",
        "<span id=\"run-quantum-workloads-with-qrmi\" />\n",
        "\n",
        "# Ejecuta cargas de trabajo cuánticas con QRMI\n",
        "\n",
        "Estimación *de tiempo de ejecución: menos de un minuto en un servid IBM Quantum® o para la sección SQD. Esta estimación no incluye el tiempo de espera en la cola ni el procesamiento clásico; la duración puede variar.*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados del aprendizaje\n",
        "\n",
        "1. El papel que desempeña QRMI como middleware entre los programadores de HPC y el hardware de « IBM Quantum »\n",
        "2. Cómo utilizar el ciclo de vida básico de QRMI (`acquire` → `task_start` → `task_status` → `task_result` → `release`) con un backend real de IBM®\n",
        "3. Cómo utilizar Qiskit de nivel superior y `SamplerV2` los envoltorios `QRMIService` que se ejecutan sobre QRMI\n",
        "4. Cómo los programadores de HPC (Slurm) incorporan recursos cuánticos a través de variables de entorno y cómo las aplicaciones los consumen\n",
        "5. Cómo ejecutar un flujo de trabajo químico completo de SQD (diagonalización cuántica basada en muestras) en N $_2$, utilizando el hardware de IBM a través de QRMI\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Requisitos previos\n",
        "\n",
        "* [Qiskit primitives (Muestreador y estimador)](/docs/guides/primitives)\n",
        "* [IBM Quantum sesiones](/docs/guides/run-jobs-session)\n",
        "* [IBM Quantum transpilación](/docs/guides/transpile)\n",
        "* [Diagonalización cuántica basada en muestras (SQD)](/docs/tutorials/sample-based-quantum-diagonalization)\n",
        "* Conocimientos básicos sobre los entornos virtuales de Python y sobre química cuántica\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "<span id=\"the-quantum-hpc-integration-challenge\" />\n",
        "\n",
        "### El reto de la integración entre la informática cuántica y la supercomputación\n",
        "\n",
        "Los flujos de trabajo de computación de alto rendimiento (HPC) suelen requerir una coordinación fluida entre los clústeres de computación clásicos y las unidades de procesamiento cuántico (QPU). Los distintos backends y servicios de hardware cuántico ofrecen mecanismos de autenticación, formatos de transmisión y API para el ciclo de vida de los trabajos diferentes. La integración de los sistemas de « IBM Quantum » en los gestores de cargas de trabajo de HPC (como Slurm) requiere una interfaz clara y estándar para la adquisición de recursos, la ejecución de trabajos y la gestión de sesiones.\n",
        "\n",
        "<span id=\"what-qrmi-is\" />\n",
        "\n",
        "### Qué es el QRMI\n",
        "\n",
        "La **Interfaz de Gestión de Recursos Cuánticos (QRMI)** es una biblioteca de middleware escrita en Rust que estandariza el acceso al hardware cuántico desde los programadores de HPC y las aplicaciones clásicas. Ofrece una única API unificada para el ciclo de vida:\n",
        "\n",
        "```\n",
        "┌─────────────────────────────────────────────────────────────────┐\n",
        "│                     HPC Application Layer                       │\n",
        "│          (Slurm job script / Python workflow / CUDA-Q)          │\n",
        "└───────────────────────────┬─────────────────────────────────────┘\n",
        "                            │  QRMI API\n",
        "                            │  acquire() / task_start() / task_result() / release()\n",
        "┌───────────────────────────▼─────────────────────────────────────┐\n",
        "│                        QRMI Core (Rust)                         │\n",
        "│            Python bindings · C bindings · Lua bindings          │\n",
        "└───────────────────────────┬─────────────────────────────────────┘\n",
        "                            │\n",
        "               IBM Quantum Compute Service / IBM Quantum System\n",
        "```\n",
        "\n",
        "QRMI se publica como un proyecto de código abierto en [github.com/qiskit-community/qrmi](https://github.com/qiskit-community/qrmi) y se describe en el artículo de presentación [arXiv:2506.10052](https://arxiv.org/abs/2506.10052).\n",
        "\n",
        "<span id=\"key-design-choices\" />\n",
        "\n",
        "### Decisiones clave de diseño\n",
        "\n",
        "**El ciclo de vida de los recursos, no la compilación de circuitos.** QRMI se encarga del ciclo de vida de adquisición, envío, consulta y liberación, y de nada más. La compilación, optimización y transpilación de circuitos se siguen realizando en la capa de aplicación (por ejemplo, Qiskit). Esto permite que la interfaz sea minimalista y modulable.\n",
        "\n",
        "**Modelo de portabilidad de proveedores.** Aunque QRMI proporciona llamadas comunes para la gestión de tareas (`acquire`, `task_start`, `task_status`, `task_result`, `release`) en todos los backends de hardware compatibles, cambiar de proveedor también requiere diferentes pasadas de compilación, la construcción de cargas útiles específicas del proveedor y la decodificación de los resultados en la capa de aplicación.\n",
        "\n",
        "**Formato de carga útil nativo de « IBM ».** Para los servidores « IBM Quantum », QRMI utiliza tres cargas útiles JSON de « OpenQASM » (`QiskitPrimitive`) que se ajustan al esquema de « Qiskit Runtime ».\n",
        "\n",
        "**Configuración mediante variables de entorno.** Las credenciales y las URL de los puntos finales se leen a partir de variables de entorno en tiempo de ejecución. En un clúster de HPC, el complemento Slurm QRMI SPANK los configura automáticamente cuando se envía un trabajo. En un cuaderno o en una sesión interactiva, se cargan desde un archivo `.env` . El código de la aplicación nunca contiene credenciales ni URL de puntos finales codificadas de forma fija.\n",
        "\n",
        "**Integración del programador de HPC a través de GRES.** Cuando un trabajo de Slurm solicita recursos cuánticos mediante la interfaz del complemento QRMI SPANK (`#SBATCH --gres=qpu:1` y `#SBATCH --qpu=ibm_kingston`), el complemento inserta `QRMI_JOB_QPU_RESOURCES` y `QRMI_JOB_QPU_TYPES` en el entorno del trabajo. Las aplicaciones realizan una llamada `get_job_qpu_resources_and_types()` para averiguar qué recursos se han asignado; no es necesario especificar nombres de backend fijos. `QRMIService` Envuelve este patrón para los usuarios de Qiskit.\n",
        "\n",
        "<span id=\"the-core-api-calls\" />\n",
        "\n",
        "### Las llamadas a la API principales\n",
        "\n",
        "| Llamada                    | Finalidad                                                                                         |\n",
        "| -------------------------- | ------------------------------------------------------------------------------------------------- |\n",
        "| `qrmi.acquire()`           | Obtiene acceso al recurso (por ejemplo, abre una sesión específica); devuelve un token de bloqueo |\n",
        "| `qrmi.target()`            | Recuperar las capacidades del backend (qubits, puertas, mapa de acoplamiento) en formato JSON     |\n",
        "| `qrmi.task_start(payload)` | Enviar un trabajo cuántico; devuelve un ID de trabajo                                             |\n",
        "| `qrmi.task_status(job_id)` | Consultar el estado de la tarea (`Queued`, `Running`, `Completed`, `Failed`)                      |\n",
        "| `qrmi.task_result(job_id)` | Recuperar los resultados de los trabajos completados como una cadena JSON sin procesar            |\n",
        "| `qrmi.task_stop(job_id)`   | Cancelar o limpiar un trabajo                                                                     |\n",
        "| `qrmi.release(lock)`       | Liberar el bloqueo del recurso (por ejemplo, cerrar la sesión)                                    |\n",
        "\n",
        "<span id=\"what-this-tutorial-covers\" />\n",
        "\n",
        "### Contenido de este tutorial\n",
        "\n",
        "Este tutorial se divide en dos partes:\n",
        "\n",
        "**Pasos 1-3 (ejemplos a pequeña escala):** Presentar la API de QRMI mediante una sencilla demostración de un circuito en estado de Bell en un hardware de IBM Quantum, abordando tanto el uso directo de primitivas de bajo nivel como el de alto nivel `QRMIService` y la integración `SamplerV2` .\n",
        "\n",
        "**Ejemplo de cálculo a gran escala:** un flujo de trabajo SQD completo para la molécula N $_2$ a a una distancia entre enlaces de 1.0 $\\AA$ (espacio activo de bases cc-pVDZ, 26 orbitales espaciales / 52 qubits), ejecutado en el hardware IBM Quantum a través de QRMI. El método SQD combina el muestreo cuántico de un ansatz LUCJ construido con y `ffsim` la recuperación de la configuración autoconsistente con `qiskit-addon-sqd`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de tener instalados los siguientes elementos.\n",
        "\n",
        "<span id=\"python-environment-setup\" />\n",
        "\n",
        "### Python configuración del entorno\n",
        "\n",
        "Hay disponibles paquetes binarios precompilados para Linux en PyPI,, por lo que la versión estándar `pip install` funciona directamente en sistemas Linux /HPC.\n",
        "\n",
        "```bash\n",
        "python3 -m venv ~/.venvs/qrmi-ibm\n",
        "source ~/.venvs/qrmi-ibm/bin/activate\n",
        "python -m pip install \"qrmi[ibm]\" python-dotenv pyscf ffsim qiskit-addon-sqd matplotlib ipykernel\n",
        "python -m ipykernel install --user --name qrmi-ibm --display-name \"QRMI IBM\"\n",
        "```\n",
        "\n",
        "<Admonition type=\"note\" title=\"Plataformas sin ruedas preinstaladas\">\n",
        "  Si compilas `pip` QRMI desde el código fuente, asegúrate de tener instalada una cadena de herramientas moderna de Rust (Rust ≥ 1.91.1, que puedes descargar desde `rustup` [rustup.rs](https://rustup.rs) ).\n",
        "</Admonition>\n",
        "\n",
        "Selecciona el núcleo **QRMI IBM** en Jupyter, reinícialo y ejecuta las celdas del cuaderno en orden. Los resultados guardados proceden de la ejecución en el equipo del colaborador; los comandos de instalación no especifican las versiones exactas utilizadas para dicha ejecución.\n",
        "\n",
        "<span id=\"credentials-required\" />\n",
        "\n",
        "### Es necesario especificar las credenciales\n",
        "\n",
        "* IBM Quantum : clave API de IAM y servicio CRN de [IBM Quantum Platform]()\n",
        "\n",
        "Para ejecutarlo de forma independiente, crea un archivo `.env` junto a este cuaderno con los siguientes valores, sustituyendo los marcadores de posición de las credenciales. No divulgues este archivo. Si seleccionas un backend diferente, actualiza tanto su nombre como los prefijos de las variables de entorno.\n",
        "\n",
        "```dotenv\n",
        "ibm_kingston_QRMI_IBM_QCS_ENDPOINT=https://quantum.cloud.ibm.com/api/v1\n",
        "ibm_kingston_QRMI_IBM_QCS_IAM_ENDPOINT=https://iam.cloud.ibm.com\n",
        "ibm_kingston_QRMI_IBM_QCS_IAM_APIKEY=<your-iam-api-key>\n",
        "ibm_kingston_QRMI_IBM_QCS_SERVICE_CRN=<your-crn-starting-with-crn:v1:>\n",
        "ibm_kingston_QRMI_IBM_QCS_SESSION_MODE=dedicated\n",
        "ibm_kingston_QRMI_IBM_QCS_SESSION_MAX_TTL=28800\n",
        "QRMI_JOB_QPU_RESOURCES=ibm_kingston\n",
        "QRMI_JOB_QPU_TYPES=ibm-quantum-compute-service\n",
        "```\n",
        "\n",
        "Para una asignación de Slurm, utiliza la configuración de recursos y las credenciales facilitadas por el clúster. El cuaderno conserva los valores ambientales existentes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-header",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuración\n",
        "\n",
        "Importa las dependencias y carga la configuración de recursos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "setup-imports",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backend: ibm_kingston\n",
            "Environment ready.\n"
          ]
        }
      ],
      "source": [
        "import os\n",
        "import time\n",
        "import json\n",
        "import numpy as np\n",
        "from dotenv import load_dotenv\n",
        "\n",
        "from qrmi import (\n",
        "    QuantumResource,\n",
        "    ResourceType,\n",
        "    Payload,\n",
        "    TaskStatus,\n",
        "    get_job_qpu_resources_and_types,\n",
        ")\n",
        "from qrmi.primitives import QRMIService\n",
        "from qrmi.primitives.ibm import SamplerV2, get_target\n",
        "\n",
        "from qiskit import QuantumCircuit, qasm3\n",
        "from qiskit.circuit.library import efficient_su2\n",
        "from qiskit.primitives.containers.sampler_pub import SamplerPub\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "# Load credentials from .env without overriding already-set scheduler environment variables\n",
        "load_dotenv(override=False)\n",
        "\n",
        "# Preserve resources if injected by Slurm SPANK plugin; fallback to default for interactive run\n",
        "BACKEND_NAME = os.environ.get(\"QRMI_JOB_QPU_RESOURCES\", \"ibm_kingston\")\n",
        "os.environ.setdefault(\"QRMI_JOB_QPU_RESOURCES\", BACKEND_NAME)\n",
        "os.environ.setdefault(\"QRMI_JOB_QPU_TYPES\", \"ibm-quantum-compute-service\")\n",
        "\n",
        "print(f\"Backend: {BACKEND_NAME}\")\n",
        "print(\"Environment ready.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-scale-header",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-examples\" />\n",
        "\n",
        "## Ejemplos a pequeña escala\n",
        "\n",
        "Los pasos 1 a 3 presentan la API de QRMI mediante circuitos sencillos. Cada paso se corresponde con una fase fundamental del ciclo de vida del QRMI en relación con el hardware de l IBM Quantum.\n",
        "\n",
        "La carga útil para estos pasos iniciales es un pequeño circuito de estado de Bell, elegido por ser rápido y económico de ejecutar.\n",
        "\n",
        "Estos ejemplos utilizan hardware porque ilustran la asignación de recursos remotos y la gestión de tareas. Un simulador de circuitos locales no valida la integración del servicio QRMI y el programador. Al ejecutar este cuaderno, se envían trabajos a IBM Quantum y se requiere acceso al backend configurado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-the-classical-problem-to-a-quantum-resource\" />\n",
        "\n",
        "### Paso 1: Asignar el problema clásico a un recurso cuántico\n",
        "\n",
        "El primer paso en cualquier flujo de trabajo de QRMI consiste en crear un objeto `QuantumResource` y comprobar que sea accesible.\n",
        "\n",
        "`get_target()` recoge la descripción del hardware del backend (número de qubits, puertas básicas, mapa de acoplamiento) y la empaqueta como un `Target` objeto de Qiskit, que el transpilador utiliza en el paso 2.\n",
        "\n",
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Paso 2: Optimizar el problema para su ejecución en hardware cuántico\n",
        "\n",
        "Antes de enviarlo, utiliza Qiskit para transpilar el circuito a la arquitectura del conjunto de instrucciones (ISA) del backend, utilizando el objeto `Target` obtenido en el paso 1.\n",
        "\n",
        "A continuación, el ejemplo crea un `Payload.QiskitPrimitive`, que agrupa la cadena de circuitos « OpenQASM 3» y los metadatos del trabajo en el esquema primitivo « IBM ».\n",
        "\n",
        "<span id=\"step-3-execute-using-qrmi-primitives\" />\n",
        "\n",
        "### Paso 3: Ejecutar utilizando primitivas QRMI\n",
        "\n",
        "Una vez creada la carga útil, el ejemplo envía el trabajo y comprueba cuándo se ha completado. `task_start()` devuelve un ID de trabajo de forma inmediata; `task_status()` se comprueba periódicamente hasta que el estado ya no sea `Queued`/`Running`. Los resultados se obtienen como una cadena JSON sin procesar y se analizan para extraer las muestras de medición.\n",
        "\n",
        "La siguiente celda agrupa la adquisición, la ejecución y la limpieza, de modo que, incluso si se producen errores tras la adquisición, la sesión perteneciente al cuaderno se libera de todos modos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "step1-ibm",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Resource id:   ibm_kingston\n",
            "Resource type: ResourceType.IBMQuantumComputeService\n",
            "Accessible:    True\n",
            "Lock token:    2ff43011-aed1-4436-a4df-40f37ec588b7\n",
            "\n",
            "Backend: ibm_kingston\n",
            "Qubits:  156\n",
            "Gates:   ['cz', 'id', 'rx', 'rz', 'rzz', 'sx', 'x', 'xslow']\n",
            "        ┌───┐      ░ ┌─┐   \n",
            "   q_0: ┤ H ├──■───░─┤M├───\n",
            "        └───┘┌─┴─┐ ░ └╥┘┌─┐\n",
            "   q_1: ─────┤ X ├─░──╫─┤M├\n",
            "             └───┘ ░  ║ └╥┘\n",
            "meas: 2/══════════════╩══╩═\n",
            "                      0  1 \n",
            "\n",
            "Transpiled gate counts: OrderedDict([('rz', 6), ('sx', 3), ('measure', 2), ('cz', 1), ('barrier', 1)])\n",
            "Payload ready\n",
            "Job submitted: dai43g8mhr3c73e7a7o0\n",
            "  Status: TaskStatus.Queued\n",
            "  Status: TaskStatus.Running\n",
            "  Status: TaskStatus.Completed\n",
            "\n",
            "Final status: TaskStatus.Completed\n",
            "\n",
            "Measurement counts: {'11': 487, '00': 254, '01': 177, '10': 106}\n",
            "\n",
            "Session released.\n"
          ]
        }
      ],
      "source": [
        "# ── IBM Quantum ───────────────────────────────────────────────────────\n",
        "qrmi = QuantumResource(BACKEND_NAME, ResourceType.IBMQuantumComputeService)\n",
        "# ResourceType.IBMQuantumSystem is the alternative for directly provisioned systems\n",
        "\n",
        "print(f\"Resource id:   {qrmi.resource_id()}\")\n",
        "print(f\"Resource type: {qrmi.resource_type()}\")\n",
        "print(f\"Accessible:    {qrmi.is_accessible()}\")\n",
        "\n",
        "# Acquire exclusive access — open try/finally immediately so every\n",
        "# subsequent failure (target retrieval, transpilation, submission) is covered.\n",
        "# Release is skipped when running under Slurm: the SPANK plugin owns the\n",
        "# session lifecycle and will release it when the job finishes.\n",
        "lock = qrmi.acquire()\n",
        "print(f\"Lock token:    {lock}\")\n",
        "try:\n",
        "    # Retrieve backend capabilities\n",
        "    transpiler_target = get_target(\n",
        "        qrmi\n",
        "    )  # calls qrmi.target() and parses the JSON\n",
        "    target_json = json.loads(qrmi.target().value)\n",
        "    config = target_json.get(\"configuration\", {})\n",
        "    print(f\"\\nBackend: {config.get('backend_name', 'unknown')}\")\n",
        "    print(f\"Qubits:  {config.get('n_qubits', 'unknown')}\")\n",
        "    print(f\"Gates:   {config.get('basis_gates', [])}\")\n",
        "\n",
        "    # ── IBM Quantum ───────────────────────────────────────────────────\n",
        "\n",
        "    # Build a Bell state circuit\n",
        "    qc = QuantumCircuit(2)\n",
        "    qc.h(0)\n",
        "    qc.cx(0, 1)\n",
        "    qc.measure_all()\n",
        "    print(qc.draw(\"text\"))\n",
        "\n",
        "    # Transpile to ISA using the target retrieved in Step 1\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=1, target=transpiler_target\n",
        "    )\n",
        "    isa_circuit = pm.run(qc)\n",
        "    print(f\"\\nTranspiled gate counts: {isa_circuit.count_ops()}\")\n",
        "\n",
        "    # Build the QRMI payload\n",
        "    # Payload.QiskitPrimitive wraps the IBM SamplerV2 input schema:\n",
        "    #   pubs: list of [qasm3_string, parameter_values]  (shots goes at top level)\n",
        "    #   program_id: \"sampler\" or \"estimator\"\n",
        "    shots = 1024\n",
        "    pub = SamplerPub.coerce((isa_circuit,), shots)\n",
        "    qasm3_str = qasm3.dumps(\n",
        "        pub.circuit,\n",
        "        disable_constants=True,\n",
        "        allow_aliasing=True,\n",
        "        experimental=qasm3.ExperimentalFeatures.SWITCH_CASE_V1,\n",
        "    )\n",
        "    # Parameter values as a flat list (empty for non-parametric circuits)\n",
        "    param_array = pub.parameter_values.as_array(\n",
        "        pub.circuit.parameters\n",
        "    ).tolist()\n",
        "\n",
        "    input_json = {\n",
        "        \"pubs\": [\n",
        "            [qasm3_str, param_array]\n",
        "        ],  # list-of-lists; shots at top level\n",
        "        \"version\": 2,\n",
        "        \"support_qiskit\": False,  # True returns binary-encoded Qiskit result\n",
        "        \"shots\": shots,\n",
        "    }\n",
        "    payload = Payload.QiskitPrimitive(\n",
        "        input=json.dumps(input_json), program_id=\"sampler\"\n",
        "    )\n",
        "    print(\"Payload ready\")\n",
        "\n",
        "    # ── IBM Quantum ───────────────────────────────────────────────────\n",
        "\n",
        "    # Submit the job\n",
        "    job_id = qrmi.task_start(payload)\n",
        "    print(f\"Job submitted: {job_id}\")\n",
        "\n",
        "    # Poll until complete\n",
        "    while True:\n",
        "        status = qrmi.task_status(job_id)\n",
        "        print(f\"  Status: {status}\")\n",
        "        if status not in [TaskStatus.Running, TaskStatus.Queued]:\n",
        "            break\n",
        "        time.sleep(5)\n",
        "\n",
        "    print(f\"\\nFinal status: {status}\")\n",
        "\n",
        "    # Retrieve results\n",
        "    # support_qiskit=False → plain JSON; parse directly without ResultDecoder\n",
        "    if status == TaskStatus.Completed:\n",
        "        raw = qrmi.task_result(job_id).value\n",
        "        result = json.loads(raw)\n",
        "        # IBM QCS plain-JSON result shape: {\"results\": [{\"data\": {\"meas\": {\"samples\": [...]}}}]}\n",
        "        # samples is a list of hex-encoded integers; decode to zero-padded bitstrings\n",
        "        samples = result[\"results\"][0][\"data\"][\"meas\"][\"samples\"]\n",
        "        num_bits = sum(reg.size for reg in isa_circuit.cregs)\n",
        "        from collections import Counter\n",
        "\n",
        "        counts = Counter(format(int(s, 16), f\"0{num_bits}b\") for s in samples)\n",
        "        print(f\"\\nMeasurement counts: {dict(counts.most_common(8))}\")\n",
        "        qrmi.task_stop(job_id)\n",
        "    else:\n",
        "        print(f\"Job did not complete. Logs:\\n{qrmi.task_logs(job_id)}\")\n",
        "\n",
        "finally:\n",
        "    # Release only in interactive sessions; under Slurm the SPANK plugin\n",
        "    # manages the session lifecycle and calling release() here would\n",
        "    # prematurely close a session it does not own.\n",
        "    if not os.environ.get(\"SLURM_JOB_ID\"):\n",
        "        qrmi.release(lock)\n",
        "        print(\"\\nSession released.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "higher-level-header",
      "metadata": {},
      "source": [
        "<span id=\"higher-level-qiskit-interface-qrmiservice-and-samplerv2\" />\n",
        "\n",
        "### Interfaz de nivel superior de Qiskit: QRMIService y SamplerV2\n",
        "\n",
        "El ciclo de vida «raw» anterior permite un control explícito sobre cada llamada. Para los flujos de trabajo estándar de Qiskit, QRMI proporciona una primitiva `SamplerV2` que implementa `BaseSamplerV2`.\n",
        "\n",
        "<Admonition type=\"note\" title=\"Gestión del ciclo de vida\">\n",
        "  `SamplerV2` se encarga de la serialización de la carga útil, el envío (`task_start`), la consulta y la decodificación de los resultados. En un entorno de procesamiento por lotes de HPC (por ejemplo, con Slurm), la asignación y la liberación las gestionan el programador y el complemento SPANK. En una sesión interactiva de « Python » que utilice objetos API directos de bajo nivel, `acquire()` y `release()` pueden utilizarse para gestionar explícitamente sesiones dedicadas.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "higher-level-sampler",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Using: ibm_kingston (ResourceType.IBMQuantumComputeService)\n",
            "Job ID: dai43jj9k43c73afhrhg | Status: JobStatus.QUEUED\n",
            "Counts (first 5): {'00010': 66, '00100': 28, '11000': 71, '00110': 23, '10100': 14}\n"
          ]
        }
      ],
      "source": [
        "# QRMIService reads QRMI_JOB_QPU_RESOURCES / QRMI_JOB_QPU_TYPES set in Setup or Slurm\n",
        "service = QRMIService()\n",
        "qrmi_svc = service.resources()[0]\n",
        "print(f\"Using: {qrmi_svc.resource_id()} ({qrmi_svc.resource_type()})\")\n",
        "\n",
        "# Build an EfficientSU2 circuit\n",
        "circuit = efficient_su2(5, entanglement=\"linear\")\n",
        "circuit.measure_all()\n",
        "param_values = np.random.rand(circuit.num_parameters)\n",
        "\n",
        "pm = generate_preset_pass_manager(\n",
        "    optimization_level=1, target=get_target(qrmi_svc)\n",
        ")\n",
        "isa_circuit = pm.run(circuit)\n",
        "\n",
        "# SamplerV2 executes jobs against the QRMI resource and decodes results into primitive containers\n",
        "sampler = SamplerV2(qrmi_svc, options={\"default_shots\": 1024})\n",
        "job = sampler.run([(isa_circuit, param_values)])\n",
        "print(f\"Job ID: {job.job_id()} | Status: {job.status()}\")\n",
        "\n",
        "# Poll with retry — re-raise immediately on permanent failures;\n",
        "# only retry on transient network/timeout errors (connection resets, 503s).\n",
        "_TRANSIENT = (\n",
        "    \"503\",\n",
        "    \"Service Unavailable\",\n",
        "    \"ConnectionError\",\n",
        "    \"TimeoutError\",\n",
        "    \"timed out\",\n",
        "    \"Connection reset\",\n",
        ")\n",
        "result = None\n",
        "for attempt in range(60):\n",
        "    try:\n",
        "        result = job.result()  # blocks until complete\n",
        "        break\n",
        "    except Exception as e:\n",
        "        if not any(tok in str(e) for tok in _TRANSIENT):\n",
        "            raise\n",
        "        print(f\"  Transient error on attempt {attempt + 1}: {e}\")\n",
        "        time.sleep(10)\n",
        "\n",
        "if result is not None:\n",
        "    counts = result[0].data.meas.get_counts()\n",
        "    print(f\"Counts (first 5): {dict(list(counts.items())[:5])}\")\n",
        "else:\n",
        "    print(\"Job did not complete after retries.\")\n",
        "\n",
        "if job.errored():\n",
        "    print(f\"Logs:\\n{job.logs()}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hpc-header",
      "metadata": {},
      "source": [
        "<span id=\"hpc-context-slurm-resource-injection\" />\n",
        "\n",
        "### Contexto de HPC: Inyección de recursos en Slurm\n",
        "\n",
        "En un clúster de HPC, los usuarios solicitan recursos cuánticos utilizando la sintaxis GRES de Slurm junto con las opciones del complemento SPANK de QRMI. El complemento gestiona automáticamente la inyección de credenciales y recursos:\n",
        "\n",
        "```bash\n",
        "#SBATCH --gres=qpu:1\n",
        "#SBATCH --qpu=ibm_kingston\n",
        "python my_workflow.py   # QRMI_JOB_QPU_RESOURCES and QRMI_JOB_QPU_TYPES are already set\n",
        "```\n",
        "\n",
        "El código de la aplicación detecta los recursos que se le han asignado en tiempo de ejecución; no hay nombres de backends codificados de forma fija:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "hpc-discovery",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Resources allocated by scheduler:\n",
            "  ibm_kingston  (ibm-quantum-compute-service)\n",
            "\n",
            "QRMIService found: ibm_kingston  accessible=True\n"
          ]
        }
      ],
      "source": [
        "# get_job_qpu_resources_and_types() reads QRMI_JOB_QPU_RESOURCES / QRMI_JOB_QPU_TYPES\n",
        "# set by the Slurm SPANK plugin (or manually above in Setup)\n",
        "qpus, qpu_types = get_job_qpu_resources_and_types()\n",
        "print(\"Resources allocated by scheduler:\")\n",
        "for qpu, qpu_type in zip(qpus, qpu_types):\n",
        "    print(f\"  {qpu}  ({qpu_type})\")\n",
        "\n",
        "# QRMIService wraps this into a list of ready QuantumResource objects\n",
        "for r in QRMIService().resources():\n",
        "    print(\n",
        "        f\"\\nQRMIService found: {r.resource_id()}  accessible={r.is_accessible()}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-scale-header",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example-sqd-on-n$_2$\" />\n",
        "\n",
        "## Ejemplo de hardware a gran escala: SQD en N $_2$\n",
        "\n",
        "Aquí reunimos todos los componentes en un flujo de trabajo completo de química cuántica a mayor escala, que se ejecuta en hardware real de IBM Quantum a través de QRMI.\n",
        "\n",
        "**SQD** combina lo siguiente:\n",
        "\n",
        "1. Muestreo cuántico de un modelo LUCJ (Local Unitary Cluster Jastrow) construido mediante e `ffsim` inicializado a partir de amplitudes CCSD\n",
        "2. Transpilación adaptada al hardware que se ajusta a la topología de red «heavy-hex» mediante `generate_lucj_pass_manager`\n",
        "3. Ejecución de muestreos en hardware de IBM Quantum, gestionado a través de y `QRMIService` QRMI `SamplerV2`\n",
        "4. Posprocesamiento clásico: recuperación autoconsistente de la configuración y diagonalización iterativa de subespacios mediante `qiskit-addon-sqd`\n",
        "\n",
        "Aplicamos el método SQD a N $_2$, con una distancia de enlace de 1.0 $\\AA$ y un espacio activo derivado del conjunto de bases `cc-pVDZ` (26 orbitales espaciales, que corresponden a 52 orbitales de espín/qubits).\n",
        "\n",
        "**Energía de referencia para el espacio activo N $_2$ /cc-pVDZ (distancia de enlace 1.0 $\\AA$ ):**\n",
        "\n",
        "* Energía de referencia (cálculo SCI independiente): **− 109.22802922 Ha**\n",
        "\n",
        "<Admonition type=\"note\" title=\"Precisión de la ejecución guardada\">\n",
        "  El programa SQD que se muestra a continuación ilustra la ejecución satisfactoria de QRMI de principio a fin en un hardware d IBM Quantum. Con una sola repetición de LUCJ y 100 000 iteraciones, el resultado se sitúa aproximadamente a 23.7 kcal/mol por encima de la energía de referencia y no alcanza la precisión química (≤ 1 kcal/mol). Modificar el `n_reps` número de disparos o el número de iteraciones del SQD podría mejorar la precisión, pero es necesario realizar más pruebas.\n",
        "</Admonition>\n",
        "\n",
        "En la ejecución guardada, el gestor de pasos `ffsim` eliminó las interacciones de espín opuesto y `(24, 24)` `(20, 20)` porque el backend no podía gestionarlas. Los resultados presentados utilizan este circuito ajustado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "large-scale-all",
      "metadata": {
        "tags": [
          "environment-ibm"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "WARN: Unable to to identify input symmetry using original axes.\n",
            "Different symmetry axes will be used.\n",
            "\n",
            "converged SCF energy = -108.929838385609\n",
            "N₂/cc-pVDZ active space: 26 orbitals (52 qubits), (5, 5) electrons\n",
            "SCF energy:       -108.92983839 Ha\n",
            "Reference energy: -109.22802922 Ha\n",
            "E(CCSD) = -109.2177884185545  E_corr = -0.2879500329450047\n",
            "CCSD energy:      -109.21778842 Ha\n",
            "Using QRMI resource: ibm_kingston\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "LUCJ circuit: 52 qubits, depth 3\n",
            "Transpiled gate counts: OrderedDict([('sx', 7041), ('rz', 6969), ('cz', 1858), ('measure', 52), ('x', 47), ('barrier', 1)])\n",
            "Job submitted via QRMI: dai43o0mhr3c73e7a81g | Status: JobStatus.QUEUED\n",
            "Waiting for results from hardware...\n",
            "Total shots collected: 100000\n",
            "Fraction of valid configurations sampled: 0.00319\n",
            "Expected fraction from uniform random:     9.6079e-07\n",
            "\n",
            "Running SQD post-processing...\n",
            "Iteration 1\n",
            "  Subsample 0: Energy = -109.09341960 Ha | Subspace dim = 208849\n",
            "  Subsample 1: Energy = -109.11738590 Ha | Subspace dim = 204304\n",
            "  Subsample 2: Energy = -109.09947704 Ha | Subspace dim = 212521\n",
            "Iteration 2\n",
            "  Subsample 0: Energy = -109.16015998 Ha | Subspace dim = 332929\n",
            "  Subsample 1: Energy = -109.16823702 Ha | Subspace dim = 319225\n",
            "  Subsample 2: Energy = -109.16189785 Ha | Subspace dim = 336400\n",
            "Iteration 3\n",
            "  Subsample 0: Energy = -109.17759299 Ha | Subspace dim = 471969\n",
            "  Subsample 1: Energy = -109.17937442 Ha | Subspace dim = 512656\n",
            "  Subsample 2: Energy = -109.17970409 Ha | Subspace dim = 504100\n",
            "Iteration 4\n",
            "  Subsample 0: Energy = -109.18410905 Ha | Subspace dim = 608400\n",
            "  Subsample 1: Energy = -109.18265405 Ha | Subspace dim = 636804\n",
            "  Subsample 2: Energy = -109.18608430 Ha | Subspace dim = 657721\n",
            "Iteration 5\n",
            "  Subsample 0: Energy = -109.18870837 Ha | Subspace dim = 846400\n",
            "  Subsample 1: Energy = -109.18890818 Ha | Subspace dim = 848241\n",
            "  Subsample 2: Energy = -109.19022232 Ha | Subspace dim = 804609\n",
            "\n",
            "=== Energy Summary (N₂/cc-pVDZ active space) ===\n",
            "SCF energy:       -108.92983839 Ha\n",
            "Reference energy: -109.22802922 Ha\n",
            "Final SQD energy: -109.19022232 Ha\n",
            "Energy error:     0.03780690 Ha (23.7238 kcal/mol)\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/run-quantum-workloads-with-qrmi/extracted-outputs/large-scale-all-3.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from qrmi.primitives.ibm import get_backend\n",
        "import math\n",
        "import os\n",
        "import time\n",
        "from functools import partial\n",
        "from dotenv import load_dotenv\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "import pyscf\n",
        "import pyscf.gto\n",
        "import pyscf.scf\n",
        "import pyscf.cc\n",
        "import pyscf.mcscf\n",
        "import pyscf.ao2mo\n",
        "\n",
        "import ffsim\n",
        "import ffsim.qiskit\n",
        "from qiskit import QuantumCircuit, QuantumRegister\n",
        "from qiskit_addon_sqd.fermion import (\n",
        "    SCIResult,\n",
        "    diagonalize_fermionic_hamiltonian,\n",
        "    solve_sci_batch,\n",
        ")\n",
        "from qrmi.primitives import QRMIService\n",
        "from qrmi.primitives.ibm import SamplerV2, get_target\n",
        "\n",
        "load_dotenv(override=False)\n",
        "os.environ.setdefault(\"QRMI_JOB_QPU_RESOURCES\", \"ibm_kingston\")\n",
        "os.environ.setdefault(\"QRMI_JOB_QPU_TYPES\", \"ibm-quantum-compute-service\")\n",
        "\n",
        "# ── Step 1: Map classical inputs to a quantum problem ─────────────────\n",
        "\n",
        "# Build N2 molecule at 1.0 Å bond distance\n",
        "mol = pyscf.gto.Mole()\n",
        "mol.build(\n",
        "    atom=[[\"N\", (0, 0, 0)], [\"N\", (1.0, 0, 0)]],\n",
        "    basis=\"cc-pvdz\",\n",
        "    symmetry=\"Dooh\",\n",
        ")\n",
        "\n",
        "# Define active space: freeze 2 core orbitals\n",
        "n_frozen = 2\n",
        "active_space = range(n_frozen, mol.nao_nr())\n",
        "\n",
        "# Get molecular integrals\n",
        "scf = pyscf.scf.RHF(mol).run()\n",
        "norb = len(active_space)\n",
        "n_electrons = int(sum(scf.mo_occ[active_space]))\n",
        "n_alpha = (n_electrons + mol.spin) // 2\n",
        "n_beta = (n_electrons - mol.spin) // 2\n",
        "nelec = (n_alpha, n_beta)\n",
        "\n",
        "cas = pyscf.mcscf.CASCI(scf, norb, nelec)\n",
        "mo = cas.sort_mo(active_space, base=0)\n",
        "hcore, nuclear_repulsion_energy = cas.get_h1cas(mo)\n",
        "eri = pyscf.ao2mo.restore(1, cas.get_h2cas(mo), norb)\n",
        "\n",
        "# Reference energy from external SCI calculation\n",
        "reference_energy = -109.22802921665716\n",
        "\n",
        "print(\n",
        "    f\"N₂/cc-pVDZ active space: {norb} orbitals ({2 * norb} qubits), {nelec} electrons\"\n",
        ")\n",
        "print(f\"SCF energy:       {scf.e_tot:.8f} Ha\")\n",
        "print(f\"Reference energy: {reference_energy:.8f} Ha\")\n",
        "\n",
        "# Get CCSD amplitudes for initializing the LUCJ ansatz\n",
        "ccsd = pyscf.cc.CCSD(\n",
        "    scf, frozen=[i for i in range(mol.nao_nr()) if i not in active_space]\n",
        ").run()\n",
        "t1 = ccsd.t1\n",
        "t2 = ccsd.t2\n",
        "print(f\"CCSD energy:      {ccsd.e_tot:.8f} Ha\")\n",
        "\n",
        "# Discover backend via QRMIService (QRMI_JOB_QPU_RESOURCES set in Setup)\n",
        "service = QRMIService()\n",
        "qrmi_sqd = service.resources()[0]\n",
        "print(f\"Using QRMI resource: {qrmi_sqd.resource_id()}\")\n",
        "\n",
        "# get_backend() wraps the QRMI resource as a Qiskit backend for layout synthesis\n",
        "\n",
        "backend = get_backend(qrmi_sqd)\n",
        "\n",
        "# Set ansatz properties\n",
        "n_reps = 1\n",
        "pairs_aa = [(p, p + 1) for p in range(norb - 1)]\n",
        "pairs_ab = None\n",
        "\n",
        "# Create pass manager adapted to hardware heavy-hex topology\n",
        "pass_manager, pairs_ab = ffsim.qiskit.generate_lucj_pass_manager(\n",
        "    backend=backend,\n",
        "    norb=norb,\n",
        "    connectivity=\"heavy-hex\",\n",
        "    interaction_pairs=(pairs_aa, pairs_ab),\n",
        "    optimization_level=3,\n",
        ")\n",
        "\n",
        "# Create the compressed LUCJ ansatz operator\n",
        "ucj_op = ffsim.UCJOpSpinBalanced.from_t_amplitudes(\n",
        "    t2=t2,\n",
        "    t1=t1,\n",
        "    n_reps=n_reps,\n",
        "    interaction_pairs=(pairs_aa, pairs_ab),\n",
        "    optimize=True,\n",
        "    options=dict(maxiter=1000),\n",
        ")\n",
        "\n",
        "# Assemble the circuit\n",
        "qubits = QuantumRegister(2 * norb, name=\"q\")\n",
        "circuit = QuantumCircuit(qubits)\n",
        "circuit.append(ffsim.qiskit.PrepareHartreeFockJW(norb, nelec), qubits)\n",
        "circuit.append(ffsim.qiskit.UCJOpSpinBalancedJW(ucj_op), qubits)\n",
        "circuit.measure_all()\n",
        "print(f\"LUCJ circuit: {circuit.num_qubits} qubits, depth {circuit.depth()}\")\n",
        "\n",
        "# ── Step 2: Optimize for quantum hardware execution ───────────────────\n",
        "\n",
        "isa_circuit = pass_manager.run(circuit)\n",
        "print(f\"Transpiled gate counts: {isa_circuit.count_ops()}\")\n",
        "\n",
        "# ── Step 3: Execute using Qiskit primitives (QRMI SamplerV2) ─────────\n",
        "\n",
        "sampler = SamplerV2(qrmi_sqd, options={\"default_shots\": 100_000})\n",
        "# sampler.options.environment.job_tags = [\"TUT_SQD\"]\n",
        "job = sampler.run([(isa_circuit,)])\n",
        "print(f\"Job submitted via QRMI: {job.job_id()} | Status: {job.status()}\")\n",
        "print(\"Waiting for results from hardware...\")\n",
        "\n",
        "_TRANSIENT = (\n",
        "    \"503\",\n",
        "    \"Service Unavailable\",\n",
        "    \"ConnectionError\",\n",
        "    \"TimeoutError\",\n",
        "    \"timed out\",\n",
        "    \"Connection reset\",\n",
        ")\n",
        "primitive_result = None\n",
        "for attempt in range(120):\n",
        "    try:\n",
        "        primitive_result = job.result()\n",
        "        break\n",
        "    except Exception as e:\n",
        "        if not any(tok in str(e) for tok in _TRANSIENT):\n",
        "            raise\n",
        "        print(f\"  Transient error on attempt {attempt + 1}: {e}\")\n",
        "        time.sleep(10)\n",
        "\n",
        "if primitive_result is None:\n",
        "    raise RuntimeError(\"Job did not complete after retries\")\n",
        "\n",
        "pub_result = primitive_result[0]\n",
        "bit_array = pub_result.data.meas\n",
        "print(f\"Total shots collected: {bit_array.num_shots}\")\n",
        "\n",
        "# ── Step 4: Post-process and return result in classical format ────────\n",
        "\n",
        "\n",
        "def is_valid_bitstring(\n",
        "    bitstring: str, norb: int, nelec: tuple[int, int]\n",
        ") -> bool:\n",
        "    n_a, n_b = nelec\n",
        "    return (\n",
        "        len(bitstring) == 2 * norb\n",
        "        and bitstring[norb:].count(\"1\") == n_a\n",
        "        and bitstring[:norb].count(\"1\") == n_b\n",
        "    )\n",
        "\n",
        "\n",
        "num_valid = sum(\n",
        "    is_valid_bitstring(b, norb, nelec) for b in bit_array.get_bitstrings()\n",
        ")\n",
        "valid_fraction = num_valid / bit_array.num_shots\n",
        "expected_random = (\n",
        "    math.comb(norb, n_alpha) * math.comb(norb, n_beta) / (2 ** (2 * norb))\n",
        ")\n",
        "print(f\"Fraction of valid configurations sampled: {valid_fraction:.5f}\")\n",
        "print(f\"Expected fraction from uniform random:     {expected_random:.4e}\")\n",
        "\n",
        "# Configure SQD eigensolver\n",
        "energy_tol = 1e-3\n",
        "occupancies_tol = 1e-3\n",
        "max_iterations = 5\n",
        "num_batches = 3\n",
        "samples_per_batch = 300\n",
        "symmetrize_spin = True\n",
        "carryover_threshold = 1e-4\n",
        "max_cycle = 200\n",
        "\n",
        "# Hartree-Fock initial occupancy guess\n",
        "initial_occupancies = (\n",
        "    np.array([1] * n_alpha + [0] * (norb - n_alpha)),\n",
        "    np.array([1] * n_beta + [0] * (norb - n_beta)),\n",
        ")\n",
        "\n",
        "sci_solver = partial(solve_sci_batch, spin_sq=0.0, max_cycle=max_cycle)\n",
        "result_history = []\n",
        "\n",
        "\n",
        "def callback(results: list[SCIResult]):\n",
        "    result_history.append(results)\n",
        "    iteration = len(result_history)\n",
        "    print(f\"Iteration {iteration}\")\n",
        "    for i, res in enumerate(results):\n",
        "        subspace_dim = np.prod(res.sci_state.amplitudes.shape)\n",
        "        print(\n",
        "            f\"  Subsample {i}: Energy = {res.energy + nuclear_repulsion_energy:.8f} Ha | Subspace dim = {subspace_dim}\"\n",
        "        )\n",
        "\n",
        "\n",
        "print(\"\\nRunning SQD post-processing...\")\n",
        "rng = np.random.default_rng(42)\n",
        "sqd_result = diagonalize_fermionic_hamiltonian(\n",
        "    hcore,\n",
        "    eri,\n",
        "    bit_array,\n",
        "    samples_per_batch=samples_per_batch,\n",
        "    norb=norb,\n",
        "    nelec=nelec,\n",
        "    num_batches=num_batches,\n",
        "    energy_tol=energy_tol,\n",
        "    occupancies_tol=occupancies_tol,\n",
        "    max_iterations=max_iterations,\n",
        "    sci_solver=sci_solver,\n",
        "    symmetrize_spin=symmetrize_spin,\n",
        "    initial_occupancies=initial_occupancies,\n",
        "    carryover_threshold=carryover_threshold,\n",
        "    callback=callback,\n",
        "    seed=rng,\n",
        ")\n",
        "\n",
        "final_energy = sqd_result.energy + nuclear_repulsion_energy\n",
        "energy_error = final_energy - reference_energy\n",
        "\n",
        "print(\"\\n=== Energy Summary (N₂/cc-pVDZ active space) ===\")\n",
        "print(f\"SCF energy:       {scf.e_tot:.8f} Ha\")\n",
        "print(f\"Reference energy: {reference_energy:.8f} Ha\")\n",
        "print(f\"Final SQD energy: {final_energy:.8f} Ha\")\n",
        "print(\n",
        "    f\"Energy error:     {energy_error:.8f} Ha ({abs(energy_error) * 627.5:.4f} kcal/mol)\"\n",
        ")\n",
        "\n",
        "# ── Visualization ─────────────────────────────────────────────────────\n",
        "\n",
        "x1 = range(len(result_history))\n",
        "min_e = [\n",
        "    min(res, key=lambda r: r.energy).energy + nuclear_repulsion_energy\n",
        "    for res in result_history\n",
        "]\n",
        "e_diff = [abs(e - reference_energy) for e in min_e]\n",
        "chem_accuracy = 0.001  # ~1 mHa / ~0.6 kcal/mol\n",
        "\n",
        "y2 = np.sum(sqd_result.orbital_occupancies, axis=0)\n",
        "x2 = range(len(y2))\n",
        "\n",
        "fig, axs = plt.subplots(1, 2, figsize=(12, 5))\n",
        "\n",
        "# Energies convergence plot\n",
        "axs[0].plot(x1, e_diff, label=\"Energy error\", marker=\"o\")\n",
        "axs[0].set_xticks(list(x1))\n",
        "axs[0].set_xticklabels(list(x1))\n",
        "axs[0].set_yscale(\"log\")\n",
        "axs[0].axhline(\n",
        "    y=chem_accuracy,\n",
        "    color=\"#BF5700\",\n",
        "    linestyle=\"--\",\n",
        "    label=\"Chemical accuracy (1 mHa)\",\n",
        ")\n",
        "axs[0].set_title(\"SQD Energy Error vs Iteration\")\n",
        "axs[0].set_xlabel(\"Iteration\")\n",
        "axs[0].set_ylabel(\"Energy Error (Ha)\")\n",
        "axs[0].legend()\n",
        "\n",
        "# Spatial orbital occupancy plot\n",
        "axs[1].bar(x2, y2, width=0.8)\n",
        "axs[1].set_xticks(list(x2)[::2])\n",
        "axs[1].set_xticklabels(list(x2)[::2])\n",
        "axs[1].set_title(\"Avg Occupancy per Spatial Orbital\")\n",
        "axs[1].set_xlabel(\"Spatial Orbital Index\")\n",
        "axs[1].set_ylabel(\"Avg Occupancy\")\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "next-steps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximos pasos\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendaciones\">\n",
        "  Si este trabajo te ha parecido interesante, quizá te interese el siguiente material:\n",
        "\n",
        "  * [Tutorial](/docs/tutorials/sample-based-quantum-diagonalization) sobre diagonalización cuántica basada en muestras: el flujo de trabajo completo de química SQD en IBM Quantum Platform, incluyendo moléculas más grandes y conjuntos de bases\n",
        "  * [Diagonalización cuántica de Krylov basada en muestras](/docs/tutorials/sample-based-krylov-quantum-diagonalization) : un método relacionado que utiliza circuitos de evolución temporal para modelos de red fermiónica\n",
        "  * [`qiskit-addon-sqd` documentación](/docs/addons/qiskit-addon-sqd) : referencia completa de la API y tutoriales adicionales sobre la biblioteca de posprocesamiento SQD\n",
        "  * [Repositorio QRMI « GitHub](https://github.com/qiskit-community/qrmi) »: código fuente y ejemplos adicionales de backend (CUDA-Q, C, Lua)\n",
        "  * [Documento de presentación general del QRMI](https://arxiv.org/abs/2506.10052) : descripción técnica de la arquitectura del QRMI y su integración con la informática de alto rendimiento (HPC)\n",
        "  * [IBM Quantum Compute Guía de sesiones de servicio](/docs/guides/run-jobs-session) : cómo se relacionan las sesiones con el QRMI `acquire`y`release` el ciclo de vida de los backends de IBM\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
}