{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "title",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Esegui carichi di lavoro quantistici con QRMI\"\n",
        "description: \"Utilizza l'interfaccia Quantum Resource Management per gestire i carichi di lavoro dell' IBM Quantum e ed eseguire un flusso di lavoro di chimica quantistica da un ambiente 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",
        "# Esegui carichi di lavoro quantistici con QRMI\n",
        "\n",
        "Stima dei tempi di *esecuzione: meno di un minuto su un hardware d IBM Quantum® e per la sezione SQD. Questa stima non tiene conto dei tempi di attesa e dell'elaborazione tradizionale; la durata dell'esecuzione può variare.*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "1. Il ruolo che QRMI svolge come middleware tra gli scheduler HPC e l'hardware \" IBM Quantum \"\n",
        "2. Come utilizzare il ciclo di vita QRMI di base (`acquire` → `task_start` → `task_status` → `task_result` → `release`) con un backend reale IBM®\n",
        "3. Come utilizzare Qiskit di livello superiore e `SamplerV2` i wrapper `QRMIService` basati su QRMI\n",
        "4. In che modo gli scheduler HPC (Slurm) integrano le risorse quantistiche tramite variabili d'ambiente e in che modo le applicazioni le utilizzano\n",
        "5. Come eseguire un flusso di lavoro completo di chimica SQD (diagonalizzazione quantistica basata su campioni) su N $_2$ utilizzando l'hardware IBM tramite QRMI\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prerequisiti\n",
        "\n",
        "* [Qiskit primitives (Campionatore e stimatore)](/docs/guides/primitives)\n",
        "* [IBM Quantum sessioni](/docs/guides/run-jobs-session)\n",
        "* [IBM Quantum transpilazione](/docs/guides/transpile)\n",
        "* [Diagonalizzazione quantistica basata su campioni (SQD)](/docs/tutorials/sample-based-quantum-diagonalization)\n",
        "* Conoscenza di base degli ambienti virtuali Python e della chimica quantistica\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "<span id=\"the-quantum-hpc-integration-challenge\" />\n",
        "\n",
        "### La sfida dell'integrazione tra quantistica e HPC\n",
        "\n",
        "I flussi di lavoro di calcolo ad alte prestazioni (HPC) richiedono spesso un coordinamento perfetto tra i cluster di calcolo classici e le unità di elaborazione quantistica (QPU). I diversi backend hardware quantistici e servizi offrono meccanismi di autenticazione, formati di trasmissione e API relative al ciclo di vita dei lavori distinti. L'integrazione dei sistemi di tipo \" IBM Quantum \" nei gestori di carichi di lavoro HPC (come Slurm) richiede un'interfaccia chiara e standardizzata per l'acquisizione delle risorse, l'esecuzione dei lavori e la gestione delle sessioni.\n",
        "\n",
        "<span id=\"what-qrmi-is\" />\n",
        "\n",
        "### Che cos’è il QRMI\n",
        "\n",
        "La **Quantum Resource Management Interface (QRMI)** è una libreria middleware scritta in Rust che standardizza l'accesso all'hardware quantistico da parte degli scheduler HPC e delle applicazioni classiche. Fornisce un'unica API unificata per il ciclo di vita:\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 è pubblicato come progetto open source all'indirizzo [github.com/qiskit-community/qrmi](https://github.com/qiskit-community/qrmi) ed è descritto nell'articolo di presentazione [arXiv:2506.10052](https://arxiv.org/abs/2506.10052).\n",
        "\n",
        "<span id=\"key-design-choices\" />\n",
        "\n",
        "### Scelte progettuali fondamentali\n",
        "\n",
        "**Il ciclo di vita delle risorse, non la compilazione dei circuiti.** QRMI gestisce il ciclo di vita \"acquisizione/invio/interrogazione/rilascio\" e nient'altro. La compilazione, l’ottimizzazione e la transpilazione dei circuiti rimangono a livello di applicazione (ad esempio, Qiskit). In questo modo l'interfaccia rimane essenziale e modulabile.\n",
        "\n",
        "**Modello di portabilità dei fornitori.** Sebbene QRMI fornisca chiamate comuni per la gestione dei lavori (`acquire`, `task_start`, `task_status`, `task_result`, `release`) su tutti i backend hardware supportati, il cambio di fornitore richiede anche diversi passaggi di compilazione, la creazione di payload specifici per ciascun fornitore e la decodifica dei risultati a livello di applicazione.\n",
        "\n",
        "**Formato nativo del payload di IBM.** Per i backend \" IBM Quantum \", QRMI utilizza OpenQASM 3 payload JSON (`QiskitPrimitive`) conformi allo schema Qiskit Runtime.\n",
        "\n",
        "**Configurazione tramite variabili d'ambiente.** Le credenziali e gli URL degli endpoint vengono letti dalle variabili d'ambiente in fase di esecuzione. In un cluster HPC, il plugin Slurm QRMI SPANK li imposta automaticamente al momento dell'invio di un lavoro. In un notebook o in una sessione interattiva, li si carica da un file `.env` . Il codice dell'applicazione non contiene mai credenziali o URL di endpoint hardcoded.\n",
        "\n",
        "**Integrazione dello scheduler HPC tramite GRES.** Quando un processo Slurm richiede risorse quantistiche utilizzando l'interfaccia del plugin QRMI SPANK (`#SBATCH --gres=qpu:1` e `#SBATCH --qpu=ibm_kingston`), il plugin inserisce `QRMI_JOB_QPU_RESOURCES` e `QRMI_JOB_QPU_TYPES` nell'ambiente del processo. Le applicazioni effettuano una chiamata per `get_job_qpu_resources_and_types()` scoprire quali risorse sono state assegnate — non è necessario specificare nomi di backend fissi. `QRMIService` fornisce un'interfaccia per questo modello agli utenti di Qiskit.\n",
        "\n",
        "<span id=\"the-core-api-calls\" />\n",
        "\n",
        "### Le chiamate API principali\n",
        "\n",
        "| Chiamata                   | Finalità                                                                                                |\n",
        "| -------------------------- | ------------------------------------------------------------------------------------------------------- |\n",
        "| `qrmi.acquire()`           | Ottiene l'accesso alla risorsa (ad esempio, apre una sessione dedicata); restituisce un token di blocco |\n",
        "| `qrmi.target()`            | Recupera le capacità del backend (qubit, porte logiche, mappa di accoppiamento) in formato JSON         |\n",
        "| `qrmi.task_start(payload)` | Invia un lavoro quantistico; restituisce un ID del lavoro                                               |\n",
        "| `qrmi.task_status(job_id)` | Stato del lavoro di sondaggio (`Queued`, `Running`, `Completed`, `Failed`)                              |\n",
        "| `qrmi.task_result(job_id)` | Recupera i risultati dei lavori completati come stringa JSON non formattata                             |\n",
        "| `qrmi.task_stop(job_id)`   | Annulla o ripulisci un lavoro                                                                           |\n",
        "| `qrmi.release(lock)`       | Rilasciare il blocco della risorsa (ad esempio, chiudere la sessione)                                   |\n",
        "\n",
        "<span id=\"what-this-tutorial-covers\" />\n",
        "\n",
        "### Argomenti trattati in questo tutorial\n",
        "\n",
        "Questo tutorial è suddiviso in due parti:\n",
        "\n",
        "**Passaggi 1–3 (esempi su piccola scala):** Presentare l'API QRMI con una semplice dimostrazione di un circuito a stati di Bell su un hardware d IBM Quantum, illustrando sia l'utilizzo diretto delle primitive a basso livello `QRMIService` sia l'integrazione `SamplerV2` a livello alto.\n",
        "\n",
        "**Esempio di calcolo su hardware su larga scala:** un flusso di lavoro SQD completo per la molecola N $_2$ a con una distanza di legame pari a 1.0 $\\AA$ (spazio attivo basato su cc-pVDZ, 26 orbitali spaziali / 52 qubit), eseguito su hardware IBM Quantum tramite QRMI. SQD combina il campionamento quantistico di un ansatz LUCJ costruito con e `ffsim` il recupero autoconsistente della configurazione con `qiskit-addon-sqd`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare questo tutorial, assicurati di avere installato quanto segue.\n",
        "\n",
        "<span id=\"python-environment-setup\" />\n",
        "\n",
        "### Python configurazione dell'ambiente\n",
        "\n",
        "Su PyPI, sono disponibili wheel binari precompilati per Linux, quindi la versione standard `pip install` funziona direttamente sui sistemi 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=\"Piattaforme senza ruote già montate\">\n",
        "  Se si compila `pip` QRMI partendo dal codice sorgente, assicurarsi di disporre di una toolchain Rust aggiornata (Rust ≥ 1.91.1, installabile tramite il `rustup` link [rustup.rs](https://rustup.rs) ).\n",
        "</Admonition>\n",
        "\n",
        "Seleziona il kernel **QRMI IBM** in Jupyter, quindi riavvialo ed esegui le celle del notebook in ordine. I risultati salvati provengono dall'esecuzione sull'hardware del collaboratore; i comandi di installazione non specificano le versioni esatte utilizzate per tale esecuzione.\n",
        "\n",
        "<span id=\"credentials-required\" />\n",
        "\n",
        "### Credenziali richieste\n",
        "\n",
        "* IBM Quantum : chiave API IAM e CRN del servizio da [IBM Quantum Platform]()\n",
        "\n",
        "Per l'esecuzione autonoma, creare un file `.env` accanto a questo notebook contenente i seguenti valori, sostituendo i segnaposto relativi alle credenziali. Non divulgare questo file. Se si seleziona un backend diverso, aggiornare sia il nome che i prefissi delle variabili d'ambiente.\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",
        "Per un'allocazione Slurm, utilizzare le impostazioni delle risorse e le credenziali fornite dal cluster. Il notebook conserva i valori ambientali esistenti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-header",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n",
        "Importa le dipendenze e carica la configurazione delle risorse.\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",
        "## Esempi su piccola scala\n",
        "\n",
        "I passaggi da 1 a 3 illustrano l'API QRMI attraverso l'uso di circuiti semplici. Ogni fase corrisponde a una fase fondamentale del ciclo di vita del QRMI in relazione all'hardware dell' IBM Quantum.\n",
        "\n",
        "Il carico utile per queste fasi iniziali è un piccolo circuito a stati di Bell, scelto per la rapidità e l'economicità della sua esecuzione.\n",
        "\n",
        "Questi esempi utilizzano l'hardware perché illustrano l'allocazione delle risorse in remoto e la gestione dei processi. Un simulatore di circuito locale non verifica la correttezza dell'integrazione tra il servizio QRMI e lo scheduler. L'esecuzione di questo notebook comporta l'invio di IBM Quantum e richiede l'accesso al backend configurato.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-the-classical-problem-to-a-quantum-resource\" />\n",
        "\n",
        "### Fase 1: Mappare il problema classico su una risorsa quantistica\n",
        "\n",
        "Il primo passo in qualsiasi flusso di lavoro QRMI consiste nel creare un oggetto `QuantumResource` e verificare che sia accessibile.\n",
        "\n",
        "`get_target()` recupera la descrizione hardware del backend (numero di qubit, porte di base, mappa di accoppiamento) e la converte in un oggetto `Target` Qiskit, che il transpiler utilizza nella Fase 2.\n",
        "\n",
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Fase 2: Ottimizzare il problema per l'esecuzione su hardware quantistico\n",
        "\n",
        "Prima dell'invio, utilizzare Qiskit per traspilare il circuito nell'architettura del set di istruzioni (ISA) del backend, utilizzando l'oggetto `Target` recuperato nel Passo 1.\n",
        "\n",
        "L'esempio crea quindi un `Payload.QiskitPrimitive`, che racchiude la stringa di circuiti OpenQASM 3 e i metadati del lavoro nello schema primitivo IBM.\n",
        "\n",
        "<span id=\"step-3-execute-using-qrmi-primitives\" />\n",
        "\n",
        "### Fase 3: Esecuzione tramite primitive QRMI\n",
        "\n",
        "Una volta creato il payload, l'esempio invia il processo e verifica quando è stato completato. `task_start()` restituisce immediatamente un ID del processo; `task_status()` viene interrogato finché lo stato non è più `Queued`/`Running`. I risultati vengono recuperati come stringa JSON non elaborata e analizzati per estrarre i campioni di misurazione.\n",
        "\n",
        "La cella seguente raggruppa le operazioni di acquisizione, esecuzione e pulizia, in modo che eventuali errori verificatisi dopo l’acquisizione comportino comunque il rilascio di una sessione di proprietà del 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",
        "### Interfaccia Qiskit di livello superiore: QRMIService e SamplerV2\n",
        "\n",
        "Il ciclo di vita grezzo sopra riportato consente un controllo esplicito su ogni chiamata. Per i flussi di lavoro standard di Qiskit, QRMI fornisce una primitiva `SamplerV2` che implementa `BaseSamplerV2`.\n",
        "\n",
        "<Admonition type=\"note\" title=\"Gestione del ciclo di vita\">\n",
        "  `SamplerV2` gestisce la serializzazione del payload, l'invio (`task_start`), il polling e la decodifica dei risultati. In un ambiente HPC batch (ad esempio, con Slurm), l'allocazione e il rilascio sono gestiti dallo scheduler e dal plugin SPANK. In una sessione interattiva di “ Python ” che utilizza oggetti API diretti di basso livello, `acquire()` e `release()` possono essere utilizzati per gestire in modo esplicito le sessioni dedicate.\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",
        "### Contesto HPC: Iniezione di risorse Slurm\n",
        "\n",
        "In un cluster HPC, gli utenti richiedono risorse quantistiche utilizzando la sintassi Slurm GRES insieme alle opzioni del plugin QRMI SPANK. Il plugin gestisce automaticamente l'inserimento delle credenziali e delle risorse:\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",
        "Il codice dell'applicazione rileva le risorse che gli sono state assegnate in fase di esecuzione — senza nomi di backend hardcoded:\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",
        "## Esempio di hardware su larga scala: SQD su N $_2$\n",
        "\n",
        "Qui abbiamo integrato tutti i componenti in un flusso di lavoro completo di chimica quantistica su scala più ampia, eseguito su hardware reale dell' IBM Quantum e tramite QRMI.\n",
        "\n",
        "**SQD** riunisce i seguenti elementi:\n",
        "\n",
        "1. Campionamento quantistico di un ansatz di tipo Local Unitary Cluster Jastrow (LUCJ) costruito utilizzando `ffsim` e inizializzato a partire dalle ampiezze CCSD\n",
        "2. Transpilazione sensibile all'hardware che si adatta alla topologia a reticolo \"heavy-hex\" tramite `generate_lucj_pass_manager`\n",
        "3. Esecuzione del campionamento su hardware IBM Quantum, gestito tramite e `QRMIService` QRMI `SamplerV2`\n",
        "4. Post-elaborazione classica: recupero della configurazione auto-coerente e diagonalizzazione iterativa del sottospazio mediante `qiskit-addon-sqd`\n",
        "\n",
        "Applichiamo il metodo SQD a N $_2$, con una distanza di legame pari a 1.0 $\\AA$ e uno spazio attivo derivato dall'insieme di basi `cc-pVDZ` (26 orbitali spaziali, corrispondenti a 52 spin-orbitali/qubit).\n",
        "\n",
        "**Energia di riferimento per N $_2$ /cc-pVDZ spazio attivo (distanza di legame 1.0 $\\AA$ ):**\n",
        "\n",
        "* Energia di riferimento (calcolo SCI separato): **− 109.22802922 Ha**\n",
        "\n",
        "<Admonition type=\"note\" title=\"Precisione della corsa salvata\">\n",
        "  Il codice SQD riportato di seguito illustra l'esecuzione end-to-end di QRMI su un hardware dell IBM Quantum. Con una singola ripetizione del LUCJ e 100.000 simulazioni, il risultato si colloca a circa 23.7 kcal/mol al di sopra dell'energia di riferimento e non raggiunge la precisione chimica (≤ 1 kcal/mol). Modificare `n_reps` il numero di scatti o il numero di iterazioni SQD potrebbe migliorare la precisione, ma richiede ulteriori test.\n",
        "</Admonition>\n",
        "\n",
        "Nella simulazione salvata, il gestore dei passaggi `ffsim` ha rimosso le interazioni con spin opposto e `(24, 24)` `(20, 20)` poiché il backend non era in grado di gestirle. I risultati riportati si basano su questo circuito modificato.\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",
        "## Passi successivi\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti contenuti:\n",
        "\n",
        "  * [Tutorial sulla diagonalizzazione quantistica basata su campioni](/docs/tutorials/sample-based-quantum-diagonalization) — il flusso di lavoro completo di chimica SQD su IBM Quantum Platform, comprese molecole più grandi e insiemi di basi\n",
        "  * [Diagonalizzazione quantistica di Krylov basata su campioni](/docs/tutorials/sample-based-krylov-quantum-diagonalization) : un metodo correlato che utilizza circuiti di evoluzione temporale per modelli a reticolo fermionico\n",
        "  * [`qiskit-addon-sqd` documentazione](/docs/addons/qiskit-addon-sqd) — riferimento completo all'API e tutorial aggiuntivi per la libreria di post-elaborazione SQD\n",
        "  * [Repository QRMI GitHub](https://github.com/qiskit-community/qrmi) — codice sorgente, esempi aggiuntivi di backend (CUDA-Q, C, Lua)\n",
        "  * [Documento di sintesi sul QRMI](https://arxiv.org/abs/2506.10052) — Descrizione tecnica dell'architettura del QRMI e dell'integrazione con l'HPC\n",
        "  * [IBM Quantum Compute Guida alle sessioni di servizio](/docs/guides/run-jobs-session) — come le sessioni si rapportano al QRMI `acquire`/`release` ciclo di vita per i backend di 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
}