{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "title",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Exécutez des charges de travail quantiques avec QRMI\"\n",
        "description: \"Utilisez l'interface Quantum Resource Management pour gérer les charges de travail d' IBM Quantum s et exécuter un workflow de chimie quantique à partir d'un environnement 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",
        "# Exécutez des charges de travail quantiques avec QRMI\n",
        "\n",
        "Estimation du temps *d'exécution : moins d'une minute sur un matériel d' IBM Quantum®, pour la section SQD. Cette estimation ne tient pas compte du temps d'attente ni du traitement classique; la durée d'exécution peut varier.*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Acquis d'apprentissage\n",
        "\n",
        "1. Le rôle joué par QRMI en tant que couche intermédiaire entre les planificateurs HPC et le matériel d' IBM Quantum\n",
        "2. Comment utiliser le cycle de vie de base du QRMI (`acquire` → `task_start` → `task_status` → `task_result` → `release`) avec un backend « real- IBM® »\n",
        "3. Comment utiliser les couches de haut niveau de Qiskit et `SamplerV2` les wrappers `QRMIService` s'appuyant sur QRMI\n",
        "4. Comment les planificateurs HPC (Slurm) intègrent les ressources quantiques via des variables d'environnement et comment les applications les exploitent\n",
        "5. Comment exécuter un workflow chimique complet de SQD (diagonalisation quantique par échantillonnage) sur N « $_2$ » à l’aide du matériel « IBM » via QRMI\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prérequis\n",
        "\n",
        "* [Qiskit primitives (Échantillonneur et estimateur)](/docs/guides/primitives)\n",
        "* [IBM Quantum sessions](/docs/guides/run-jobs-session)\n",
        "* [IBM Quantum transpilation](/docs/guides/transpile)\n",
        "* [Diagonalisation quantique par échantillonnage (SQD)](/docs/tutorials/sample-based-quantum-diagonalization)\n",
        "* Connaissances de base des environnements virtuels d' Python et de la chimie quantique\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Arrière-plan\n",
        "\n",
        "<span id=\"the-quantum-hpc-integration-challenge\" />\n",
        "\n",
        "### Le défi de l'intégration entre l'informatique quantique et le calcul haute performance (HPC)\n",
        "\n",
        "Les flux de travail en calcul haute performance (HPC) nécessitent souvent une coordination fluide entre les clusters de calcul classiques et les unités de traitement quantique (QPU). Les différents backends et services de matériel quantique proposent des mécanismes d'authentification, des formats de transmission et des API de gestion du cycle de vie des tâches distincts. L'intégration des systèmes d' IBM Quantum s dans les gestionnaires de charges de travail HPC (tels que Slurm) nécessite une interface simple et standardisée pour l'acquisition des ressources, l'exécution des tâches et la gestion des sessions.\n",
        "\n",
        "<span id=\"what-qrmi-is\" />\n",
        "\n",
        "### Qu'est-ce que le QRMI?\n",
        "\n",
        "L'interface **QRMI (Quantum Resource Management Interface)** est une bibliothèque middleware écrite en Rust qui uniformise l'accès au matériel quantique depuis les planificateurs HPC et les applications classiques. Elle expose une API unique et unifiée pour la gestion du cycle de vie :\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 est publié sous forme de projet open source à l'adresse [github.com/qiskit-community/qrmi](https://github.com/qiskit-community/qrmi) et est décrit dans l'article de synthèse [arXiv:2506.10052](https://arxiv.org/abs/2506.10052).\n",
        "\n",
        "<span id=\"key-design-choices\" />\n",
        "\n",
        "### Choix clés en matière de conception\n",
        "\n",
        "**Le cycle de vie des ressources, et non la compilation des circuits.** QRMI gère le cycle de vie « acquisition/soumission/interrogation/libération » et rien d'autre. La compilation, l'optimisation et la transpilation des circuits restent du ressort de la couche applicative (par exemple, Qiskit). Cela permet de conserver une interface minimaliste et modulable.\n",
        "\n",
        "**Modèle de portabilité des fournisseurs.** Bien que QRMI fournisse des appels communs de gestion des tâches (`acquire`, `task_start`, `task_status`, `task_result`, `release`) sur l'ensemble des backends matériels pris en charge, le changement de fournisseur nécessite également différents cycles de compilation, la construction d'une charge utile spécifique au fournisseur et le décodage des résultats au niveau de la couche applicative.\n",
        "\n",
        "**Format natif de la charge utile « IBM ».** Pour les backends « IBM Quantum », QRMI utilise des charges utiles JSON de type « OpenQASM 3 » (`QiskitPrimitive`) conformes au schéma Qiskit Runtime.\n",
        "\n",
        "**Configuration via des variables d'environnement.** Les identifiants et les URL des points de terminaison sont lus à partir des variables d'environnement lors de l'exécution. Dans un cluster HPC, le plugin Slurm QRMI SPANK les configure automatiquement lors de l'envoi d'un travail. Dans un notebook ou lors d'une session interactive, vous les chargez à partir d'un fichier `.env` . Le code de l'application ne contient jamais d'identifiants ni d'URL de point de terminaison codés en dur.\n",
        "\n",
        "**Intégration d'un planificateur HPC via GRES.** Lorsqu'une tâche Slurm sollicite des ressources Quantum via l'interface du plugin QRMI SPANK (`#SBATCH --gres=qpu:1` et `#SBATCH --qpu=ibm_kingston`), le plugin injecte `QRMI_JOB_QPU_RESOURCES` et `QRMI_JOB_QPU_TYPES` dans l'environnement de la tâche. Les applications appellent cette interface pour `get_job_qpu_resources_and_types()` connaître les ressources qui leur ont été attribuées — aucun nom de backend codé en dur n'est nécessaire. `QRMIService` fournit une interface pour ce modèle à l'intention des utilisateurs de Qiskit.\n",
        "\n",
        "<span id=\"the-core-api-calls\" />\n",
        "\n",
        "### Les appels d'API principaux\n",
        "\n",
        "| Appel                      | Fonction                                                                                                 |\n",
        "| -------------------------- | -------------------------------------------------------------------------------------------------------- |\n",
        "| `qrmi.acquire()`           | Obtient l'accès à la ressource (par exemple, ouvre une session dédiée); renvoie un jeton de verrouillage |\n",
        "| `qrmi.target()`            | Récupérer les capacités du backend (qubits, portes, carte de couplage) au format JSON                    |\n",
        "| `qrmi.task_start(payload)` | Soumettre une tâche quantique; renvoie un identifiant de tâche                                           |\n",
        "| `qrmi.task_status(job_id)` | État de la tâche de sondage (`Queued`, `Running`, `Completed`, `Failed`)                                 |\n",
        "| `qrmi.task_result(job_id)` | Récupérer les résultats des tâches terminées sous forme de chaîne JSON brute                             |\n",
        "| `qrmi.task_stop(job_id)`   | Annuler ou nettoyer une tâche                                                                            |\n",
        "| `qrmi.release(lock)`       | Libérer le verrou de la ressource (par exemple, fermer la session)                                       |\n",
        "\n",
        "<span id=\"what-this-tutorial-covers\" />\n",
        "\n",
        "### Contenu de ce tutoriel\n",
        "\n",
        "Ce tutoriel est divisé en deux parties :\n",
        "\n",
        "**Étapes 1 à 3 (exemples à petite échelle) :** Présenter l'API QRMI à l'aide d'une démonstration simple utilisant un circuit à états de Bell sur le matériel d' IBM Quantum, en abordant à la fois l'utilisation directe des primitives de bas niveau ainsi que `QRMIService` l'intégration `SamplerV2` de haut niveau.\n",
        "\n",
        "**Exemple de calcul matériel à grande échelle :** un workflow SQD complet pour la molécule N $_2$ e à une distance de liaison de 1.0 $\\AA$ (espace actif à base cc-pVDZ, 26 orbitales spatiales / 52 qubits), exécuté sur le matériel IBM Quantum via QRMI. La méthode SQD combine l'échantillonnage quantique d'un ansatz LUCJ construit à l'aide de `ffsim` et la récupération de configuration auto-cohérente à l'aide de `qiskit-addon-sqd`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Exigences\n",
        "\n",
        "Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont bien installés.\n",
        "\n",
        "<span id=\"python-environment-setup\" />\n",
        "\n",
        "### Python configuration de l'environnement\n",
        "\n",
        "Des paquets binaires précompilés sont disponibles pour Linux sur PyPI,; ainsi, la version standard `pip install` fonctionne directement sur les systèmes 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=\"Plateformes sans roues intégrées\">\n",
        "  Si vous compilez `pip` QRMI à partir du code source, assurez-vous de disposer d'une chaîne d'outils Rust récente (Rust ≥ 1.91.1, à installer via [rustup.rs](https://rustup.rs)`rustup` ).\n",
        "</Admonition>\n",
        "\n",
        "Sélectionnez le noyau **QRMI IBM** dans Jupyter, puis redémarrez-le et exécutez les cellules du notebook dans l'ordre. Les résultats enregistrés proviennent d'une exécution sur le matériel du contributeur; les commandes d'installation ne précisent pas les versions exactes utilisées pour cette exécution.\n",
        "\n",
        "<span id=\"credentials-required\" />\n",
        "\n",
        "### Justificatifs requis\n",
        "\n",
        "* IBM Quantum : clé API IAM et service CRN, disponible sur [IBM Quantum Platform]()\n",
        "\n",
        "Pour une exécution autonome, créez un fichier `.env` à côté de ce notebook contenant les valeurs suivantes, en remplaçant les espaces réservés aux identifiants. Ce fichier doit rester confidentiel. Si vous choisissez un autre backend, modifiez à la fois son nom et les préfixes des variables d'environnement.\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",
        "Pour une allocation Slurm, utilisez les paramètres de ressources et les identifiants fournis par le cluster. Le bloc-notes conserve les valeurs d'environnement existantes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-header",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuration\n",
        "\n",
        "Importez les dépendances et chargez la configuration des ressources.\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",
        "## Exemples à petite échelle\n",
        "\n",
        "Les étapes 1 à 3 présentent l'API QRMI à l'aide de circuits simples. Chaque étape correspond à une phase clé du cycle de vie du QRMI sur le matériel « IBM Quantum ».\n",
        "\n",
        "La charge utile pour ces premières étapes est un petit circuit à état de Bell, choisi pour sa rapidité et son faible coût d'exécution.\n",
        "\n",
        "Ces exemples font appel au matériel, car ils illustrent l'allocation de ressources à distance et la gestion des tâches. Un simulateur de circuit local ne permet pas de valider l'intégration du service QRMI et du planificateur. L'exécution de ce notebook lance des tâches IBM Quantum et nécessite un accès au backend configuré.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-the-classical-problem-to-a-quantum-resource\" />\n",
        "\n",
        "### Étape 1 : Associer le problème classique à une ressource quantique\n",
        "\n",
        "La première étape de tout workflow QRMI consiste à créer un objet `QuantumResource` et à vérifier qu'il est accessible.\n",
        "\n",
        "`get_target()` récupère la description matérielle du backend (nombre de qubits, portes de base, carte de couplage) et la convertit en un objet `Target` Qiskit, que le transpilateur utilise à l'étape 2.\n",
        "\n",
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Étape 2 : Optimiser le problème en vue de son exécution sur un matériel quantique\n",
        "\n",
        "Avant de valider, utilisez Qiskit pour transcompiler le circuit vers l'architecture du jeu d'instructions (ISA) du backend, à l'aide de l'objet `Target` récupéré à l'étape 1.\n",
        "\n",
        "L'exemple crée ensuite un objet `Payload.QiskitPrimitive`qui encapsule la chaîne de circuits « OpenQASM 3 » ainsi que les métadonnées de la tâche dans le schéma primitif « IBM ».\n",
        "\n",
        "<span id=\"step-3-execute-using-qrmi-primitives\" />\n",
        "\n",
        "### Étape 3 : Exécution à l'aide des primitives QRMI\n",
        "\n",
        "Une fois la charge utile créée, l'exemple envoie la tâche et vérifie si elle est terminée. `task_start()` renvoie immédiatement un identifiant de tâche; `task_status()` est interrogé jusqu'à ce que le statut ne soit plus `Queued`/`Running`. Les résultats sont récupérés sous la forme d'une chaîne JSON brute, puis analysés pour en extraire les échantillons de mesure.\n",
        "\n",
        "La cellule suivante regroupe les étapes d'acquisition, d'exécution et de nettoyage, de sorte que les échecs survenant après l'acquisition libèrent tout de même une session appartenant au notebook.\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",
        "### Interface Qiskit de haut niveau : QRMIService et SamplerV2\n",
        "\n",
        "Le cycle de vie « brut » décrit ci-dessus permet un contrôle explicite sur chaque appel. Pour les workflows Qiskit standard, QRMI fournit une primitive `SamplerV2` permettant de mettre en œuvre `BaseSamplerV2`.\n",
        "\n",
        "<Admonition type=\"note\" title=\"Gestion du cycle de vie\">\n",
        "  `SamplerV2` gère la sérialisation de la charge utile, l'envoi (`task_start`), l'interrogation et le décodage des résultats. Dans un environnement HPC en mode batch (par exemple, avec Slurm), l'allocation et la libération des ressources sont gérées par le planificateur et le plugin SPANK. Dans une session interactive d’ Python, lorsque l’on utilise des objets API de bas niveau, `acquire()` et `release()` peuvent être utilisés pour gérer explicitement des sessions dédiées.\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",
        "### Contexte HPC : injection de ressources Slurm\n",
        "\n",
        "Dans un cluster HPC, les utilisateurs demandent des ressources quantiques en utilisant la syntaxe Slurm GRES ainsi que les options du plugin QRMI SPANK. Le plugin gère automatiquement l'injection des identifiants et des ressources :\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",
        "Le code de l'application détecte les ressources qui lui sont allouées au moment de l'exécution — aucun nom de backend n'est codé en dur :\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",
        "## Exemple de matériel à grande échelle : SQD sur N $_2$\n",
        "\n",
        "Nous avons ici regroupé tous ces composants pour former un workflow complet de chimie quantique à plus grande échelle, exécuté sur du matériel d’ IBM Quantum s réels via QRMI.\n",
        "\n",
        "**SQD** réunit les éléments suivants :\n",
        "\n",
        "1. Échantillonnage quantique d'un modèle LUCJ (Local Unitary Cluster Jastrow) construit à l'aide de `ffsim` et initialisé à partir d'amplitudes CCSD\n",
        "2. Transpilation tenant compte du matériel et respectant la topologie en treillis « heavy-hex » via `generate_lucj_pass_manager`\n",
        "3. Exécution de l'échantillonnage sur le matériel « IBM Quantum » géré via et `QRMIService` QRMI `SamplerV2`\n",
        "4. Post-traitement classique : restauration auto-cohérente de la configuration et diagonalisation itérative des sous-espaces à l'aide de `qiskit-addon-sqd`\n",
        "\n",
        "Nous appliquons la méthode SQD à N $_2$, à une distance de liaison de 1.0 $\\AA$, avec un espace actif dérivé de l'ensemble de bases `cc-pVDZ` (26 orbitales spatiales, correspondant à 52 orbitales spin-orbitales/qubits).\n",
        "\n",
        "**Énergie de référence pour N $_2$ /cc-pVDZ espace actif (distance de liaison 1.0 $\\AA$ ) :**\n",
        "\n",
        "* Énergie de référence (calcul SCI distinct) : **− 109.22802922 Ha**\n",
        "\n",
        "<Admonition type=\"note\" title=\"Précision de la session enregistrée\">\n",
        "  Le programme SQD ci-dessous illustre une exécution QRMI de bout en bout réussie sur le matériel d' IBM Quantum. Avec une seule itération LUCJ et 100 000 itérations, le résultat se situe à environ 23.7 kcal/mol au-dessus de l'énergie de référence et n'atteint pas la précision chimique (≤ 1 kcal/mol). Modifier le `n_reps` nombre de prises de vue ou le nombre d'itérations SQD pourrait améliorer la précision, mais cela nécessite des tests supplémentaires.\n",
        "</Admonition>\n",
        "\n",
        "Dans la simulation enregistrée, le gestionnaire de passes `ffsim` a supprimé les interactions de spin opposé et `(24, 24)` `(20, 20)` car le moteur de calcul ne pouvait pas les prendre en charge. Les résultats présentés ici utilisent ce circuit ajusté.\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",
        "## Etapes suivantes\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommandations\">\n",
        "  Si ce travail vous a paru intéressant, les documents suivants pourraient vous intéresser :\n",
        "\n",
        "  * [Tutoriel sur la diagonalisation quantique basée sur des échantillons](/docs/tutorials/sample-based-quantum-diagonalization) — le flux de travail complet de chimie SQD sur IBM Quantum Platform, incluant des molécules plus complexes et des ensembles de bases\n",
        "  * [Diagonalisation quantique de Krylov par échantillonnage](/docs/tutorials/sample-based-krylov-quantum-diagonalization) — une méthode apparentée utilisant des circuits d'évolution temporelle pour les modèles de réseaux fermioniques\n",
        "  * [`qiskit-addon-sqd` documentation](/docs/addons/qiskit-addon-sqd) — référence complète de l'API et tutoriels supplémentaires pour la bibliothèque de post-traitement SQD\n",
        "  * [Référentiel QRMI GitHub](https://github.com/qiskit-community/qrmi) — code source, exemples supplémentaires de back-end (CUDA-Q, C, Lua)\n",
        "  * [Document de présentation du QRMI](https://arxiv.org/abs/2506.10052) — description technique de l'architecture du QRMI et de son intégration au calcul haute performance (HPC)\n",
        "  * [IBM Quantum Compute Guide des sessions de service](/docs/guides/run-jobs-session) — lien entre les sessions et le QRMI `acquire`/`release` cycle de vie pour les backends d’ 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
}