{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "title",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Quanten-Workloads mit QRMI ausführen\"\n",
        "description: \"Verwenden Sie die Quantum Resource Management Interface, um „ IBM Quantum “-Workloads zu verwalten und einen Workflow für Quantenchemie aus einer HPC-Umgebung heraus auszuführen.\"\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",
        "# Quanten-Workloads mit QRMI ausführen\n",
        "\n",
        "Geschätzte *Laufzeit: weniger als eine Minute auf „ IBM Quantum® “-Hardware für den SQD-Abschnitt. Diese Schätzung berücksichtigt weder Wartezeiten noch die klassische Bearbeitung; die Laufzeit kann variieren.*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Lernziele\n",
        "\n",
        "1. Die Rolle, die QRMI als Middleware zwischen HPC-Schedulern und der „ IBM Quantum “-Hardware spielt\n",
        "2. So nutzen Sie den zentralen QRMI-Lebenszyklus (`acquire` → `task_start` → `task_status` → → `task_result` `release`) in Verbindung mit einem echten „ IBM® “-Backend\n",
        "3. So verwenden Sie die übergeordneten Qiskit- und `SamplerV2` `QRMIService` -Wrapper auf Basis von QRMI\n",
        "4. Wie HPC-Scheduler (Slurm) Quantenressourcen über Umgebungsvariablen bereitstellen und wie Anwendungen diese nutzen\n",
        "5. So führen Sie einen vollständigen SQD-Chemie-Workflow (Sample-based Quantum Diagonalization) auf N $_2$ n unter Verwendung von „ IBM “-Hardware über QRMI durch\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Voraussetzungen\n",
        "\n",
        "* [Qiskit primitives (Stichprobenverfahren und Schätzer)](/docs/guides/primitives)\n",
        "* [IBM Quantum Sitzungen](/docs/guides/run-jobs-session)\n",
        "* [IBM Quantum Transpilation](/docs/guides/transpile)\n",
        "* [Sample-basierte Quantendiagonalisierung (SQD)](/docs/tutorials/sample-based-quantum-diagonalization)\n",
        "* Grundlegende Kenntnisse im Umgang mit virtuellen Umgebungen von „ Python “ und in der Quantenchemie\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Hintergrund\n",
        "\n",
        "<span id=\"the-quantum-hpc-integration-challenge\" />\n",
        "\n",
        "### Die Herausforderung der Integration von Quanten-HPC\n",
        "\n",
        "Workflows im Bereich des Hochleistungsrechnens (HPC) erfordern häufig eine nahtlose Koordination zwischen klassischen Rechenclustern und Quantenverarbeitungseinheiten (QPUs). Verschiedene Quantenhardware-Backends und -Dienste bieten unterschiedliche Authentifizierungsmechanismen, Datenformate und APIs für den Job-Lebenszyklus. Die Integration von „ IBM Quantum “-Systemen in HPC-Workload-Manager (wie beispielsweise Slurm) erfordert eine übersichtliche, standardisierte Schnittstelle für die Ressourcenzuweisung, die Jobausführung und die Sitzungsverwaltung.\n",
        "\n",
        "<span id=\"what-qrmi-is\" />\n",
        "\n",
        "### Was ist QRMI?\n",
        "\n",
        "Die **Quantum Resource Management Interface (QRMI)** ist eine in Rust geschriebene Middleware-Bibliothek, die den Zugriff auf Quantenhardware von HPC-Schedulern und klassischen Anwendungen aus vereinheitlicht. Es stellt eine einzige, einheitliche Lebenszyklus-API bereit:\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 wird als Open-Source-Projekt unter [github.com/qiskit-community/qrmi](https://github.com/qiskit-community/qrmi) veröffentlicht und in der Übersichtsarbeit [arXiv:2506.10052](https://arxiv.org/abs/2506.10052) beschrieben.\n",
        "\n",
        "<span id=\"key-design-choices\" />\n",
        "\n",
        "### Wichtige Gestaltungsentscheidungen\n",
        "\n",
        "**Der Lebenszyklus der Ressource, nicht die Schaltkreiskompilierung.** QRMI verwaltet den Lebenszyklus „Erfassen/Übermitteln/Abfragen/Freigeben“ und sonst nichts. Die Schaltkreiskompilierung, -optimierung und -transpilation erfolgen weiterhin auf der Anwendungsebene (beispielsweise in Qiskit). Dadurch bleibt die Benutzeroberfläche minimalistisch und modular.\n",
        "\n",
        "**Modell zur Anbieterunabhängigkeit.** Zwar stellt QRMI für alle unterstützten Hardware-Backends gemeinsame Aufrufe zur Auftragsverwaltung (`acquire`, `task_start`, `task_status`, `task_result`, `release`) bereit, doch erfordert ein Wechsel des Anbieters auch unterschiedliche Kompilierungsdurchläufe, eine anbieterspezifische Erstellung der Nutzdaten sowie die Dekodierung der Ergebnisse in der Anwendungsschicht.\n",
        "\n",
        "**Natives „ IBM “-Nutzdatenformat.** Für „ IBM Quantum “-Backends verwendet QRMI JSON-Nutzdaten vom Typ „ OpenQASM “ 3 (`QiskitPrimitive`), die dem Schema „ Qiskit Runtime “ entsprechen.\n",
        "\n",
        "**Konfiguration über Umgebungsvariablen.** Anmeldedaten und Endpunkt-URLs werden zur Laufzeit aus Umgebungsvariablen ausgelesen. In einem HPC-Cluster werden diese vom Slurm-QRMI-SPANK-Plugin automatisch festgelegt, wenn ein Job gestartet wird. In einem Notebook oder einer interaktiven Sitzung laden Sie sie aus einer Datei `.env` . Der Anwendungscode enthält niemals fest codierte Anmeldedaten oder Endpunkt-URLs.\n",
        "\n",
        "**Integration des HPC-Schedulers über GRES.** Wenn ein Slurm-Job über die QRMI-SPANK-Plugin-Schnittstelle (`#SBATCH --gres=qpu:1` und `#SBATCH --qpu=ibm_kingston`) Quantum-Ressourcen anfordert, fügt das Plugin `QRMI_JOB_QPU_RESOURCES` und `QRMI_JOB_QPU_TYPES` in die Jobumgebung ein. Anwendungen rufen die Schnittstelle auf, um `get_job_qpu_resources_and_types()` zu ermitteln, welche Ressourcen zugewiesen wurden – fest codierte Backend-Namen sind nicht erforderlich. `QRMIService` verpackt dieses Muster für Qiskit-Nutzer.\n",
        "\n",
        "<span id=\"the-core-api-calls\" />\n",
        "\n",
        "### Die wichtigsten API-Aufrufe\n",
        "\n",
        "| Anruf                      | Zweck                                                                                                                  |\n",
        "| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |\n",
        "| `qrmi.acquire()`           | Erwirbt Zugriff auf die Ressource (beispielsweise durch Öffnen einer dedizierten Sitzung); gibt ein Sperr-Token zurück |\n",
        "| `qrmi.target()`            | Backend-Funktionen (Qubits, Gatter, Kopplungskarte) als JSON abrufen                                                   |\n",
        "| `qrmi.task_start(payload)` | Einen Quantenjob übermitteln; gibt eine Job-ID zurück                                                                  |\n",
        "| `qrmi.task_status(job_id)` | Status der Abfrage abrufen (`Queued`, `Running`, `Completed`, `Failed`)                                                |\n",
        "| `qrmi.task_result(job_id)` | Ergebnisse abgeschlossener Aufträge als JSON-Rohzeichenfolge abrufen                                                   |\n",
        "| `qrmi.task_stop(job_id)`   | Einen Auftrag abbrechen oder bereinigen                                                                                |\n",
        "| `qrmi.release(lock)`       | Die Ressourcensperre aufheben (beispielsweise die Sitzung schließen)                                                   |\n",
        "\n",
        "<span id=\"what-this-tutorial-covers\" />\n",
        "\n",
        "### Inhalt dieses Tutorials\n",
        "\n",
        "Dieses Tutorial gliedert sich in zwei Teile:\n",
        "\n",
        "**Schritte 1–3 (Beispiele im kleinen Maßstab):** Einführung in die QRMI-API anhand einer einfachen Demonstration einer Schaltung im Bell-Zustand auf der „ IBM Quantum “-Hardware, wobei sowohl die direkte Verwendung von Primitiven auf niedriger Ebene als auch die Verwendung auf hoher Ebene `QRMIService` sowie die `SamplerV2` Integration behandelt werden.\n",
        "\n",
        "**Beispiel für groß angelegte Hardware-Berechnungen:** Ein vollständiger SQD-Workflow für das N $_2$ -Molekül bei einem Bindungsabstand von 1.0 $\\AA$ (Basisraum „ cc-pVDZ “, 26 räumliche Orbitale / 52 Qubits), ausgeführt auf IBM Quantum -Hardware über QRMI. SQD kombiniert die Quantenabtastung eines mit konstruierten LUCJ-Ansatzes `ffsim` mit der selbstkonsistenten Konfigurationswiederherstellung mittels `qiskit-addon-sqd`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Anforderungen\n",
        "\n",
        "Bevor Sie mit dieser Anleitung beginnen, stellen Sie bitte sicher, dass die folgenden Komponenten installiert sind.\n",
        "\n",
        "<span id=\"python-environment-setup\" />\n",
        "\n",
        "### Python Einrichtung der Umgebung\n",
        "\n",
        "Vorkompilierte Binär-Wheels für „ Linux “ sind unter PyPI, verfügbar, sodass die Standardversion direkt auf Linux /HPC-Systemen `pip install` läuft.\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=\"Plattformen ohne vorgefertigte Räder\">\n",
        "  Wenn Sie QRMI aus dem Quellcode kompilieren `pip` , stellen Sie sicher, dass Sie über eine moderne Rust-Toolchain (Rust ≥ 1.91.1 verfügen, die über `rustup` [rustup.rs](https://rustup.rs) installiert wurde.\n",
        "</Admonition>\n",
        "\n",
        "Wählen Sie in Jupyter den **QRMI- IBM** -Kernel aus, starten Sie ihn anschließend neu und führen Sie die Notebook-Zellen der Reihe nach aus. Die gespeicherten Ausgaben stammen aus dem Hardware-Lauf des Mitwirkenden; in den Installationsbefehlen sind die genauen Versionen, die für diesen Lauf verwendet wurden, nicht angegeben.\n",
        "\n",
        "<span id=\"credentials-required\" />\n",
        "\n",
        "### Berechtigungsnachweise erforderlich\n",
        "\n",
        "* IBM Quantum : IAM-API-Schlüssel und Service- CRN en von [IBM Quantum Platform]()\n",
        "\n",
        "Für die eigenständige Ausführung erstellen Sie neben diesem Notizbuch eine Datei `.env` mit den folgenden Werten und ersetzen Sie dabei die Platzhalter für die Anmeldedaten. Behandle diese Datei vertraulich. Wenn Sie ein anderes Backend auswählen, aktualisieren Sie sowohl dessen Namen als auch die Präfixe der Umgebungsvariablen.\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",
        "Verwenden Sie für eine Slurm-Zuweisung die vom Cluster bereitgestellten Ressourceneinstellungen und Anmeldedaten. Das Notizbuch behält die vorhandenen Umgebungswerte bei.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-header",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Einrichtung\n",
        "\n",
        "Importieren Sie die Abhängigkeiten und laden Sie die Ressourcenkonfiguration.\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",
        "## Beispiele im kleinen Maßstab\n",
        "\n",
        "In den Schritten 1–3 wird die QRMI-API anhand einfacher Schaltungen vorgestellt. Jeder Schritt entspricht einer Kernphase des QRMI-Lebenszyklus in Bezug auf die „ IBM Quantum “-Hardware.\n",
        "\n",
        "Die Nutzlast für diese ersten Schritte ist eine kleine Bell-Zustandsschaltung, die so ausgewählt wurde, dass sie schnell und kostengünstig betrieben werden kann.\n",
        "\n",
        "In diesen Beispielen wird Hardware verwendet, da sie die Zuweisung von Remote-Ressourcen und die Auftragsverwaltung veranschaulichen. Ein lokaler Schaltungssimulator überprüft die Integration des QRMI-Dienstes und des Schedulers nicht. Durch die Ausführung dieses Notebooks werden „ IBM Quantum “-Jobs übermittelt, wofür Zugriff auf das konfigurierte Backend erforderlich ist.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-the-classical-problem-to-a-quantum-resource\" />\n",
        "\n",
        "### Schritt 1: Das klassische Problem auf eine Quantenressource abbilden\n",
        "\n",
        "Der erste Schritt in jedem QRMI-Workflow besteht darin, ein Objekt `QuantumResource` anzulegen und zu überprüfen, ob es zugänglich ist.\n",
        "\n",
        "`get_target()` ruft die Hardwarebeschreibung des Backends ab (Anzahl der Qubits, Basisgatter, Kopplungskarte) und verpackt sie als Qiskit-Objekt `Target` , das der Transpiler in Schritt 2 verwendet.\n",
        "\n",
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware\n",
        "\n",
        "Verwenden Sie vor der Einreichung Qiskit, um die Schaltung unter Verwendung des in Schritt 1 abgerufenen Objekts `Target` in die Befehlssatzarchitektur (ISA) des Backends zu transpilieren.\n",
        "\n",
        "Im Beispiel wird anschließend ein erstellt `Payload.QiskitPrimitive`, der die Schaltkreis-Zeichenkette „ OpenQASM 3“ sowie die Job-Metadaten in das Primitivschema „ IBM “ einbindet.\n",
        "\n",
        "<span id=\"step-3-execute-using-qrmi-primitives\" />\n",
        "\n",
        "### Schritt 3: Ausführung mithilfe von QRMI-Primitiven\n",
        "\n",
        "Nachdem die Nutzlast erstellt wurde, übermittelt das Beispiel den Auftrag und fragt ab, ob dieser abgeschlossen ist. `task_start()` gibt sofort eine Job-ID zurück; `task_status()` wird so lange abgefragt, bis der Status nicht mehr `Queued`/ ist`Running`. Die Ergebnisse werden als JSON-Rohzeichenfolge abgerufen und geparst, um Messwerte zu extrahieren.\n",
        "\n",
        "Die folgende Zelle fasst die Erfassung, Ausführung und Bereinigung zusammen, sodass auch bei Fehlern nach der Erfassung eine dem Notebook gehörende Sitzung freigegeben wird.\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",
        "### Qiskit-Schnittstelle auf höherer Ebene: QRMIService und SamplerV2\n",
        "\n",
        "Der oben dargestellte „Raw“-Lebenszyklus ermöglicht eine explizite Steuerung jedes Aufrufs. Für Standard-Qiskit-Workflows stellt QRMI eine Primitive `SamplerV2` zur Implementierung bereit `BaseSamplerV2`.\n",
        "\n",
        "<Admonition type=\"note\" title=\"Lebenszyklusmanagement\">\n",
        "  `SamplerV2` übernimmt die Serialisierung der Nutzdaten, die Übermittlung (`task_start`), die Abfrage und die Dekodierung der Ergebnisse. In einer HPC-Batch-Umgebung (beispielsweise mit Slurm) werden die Zuweisung und Freigabe von Ressourcen durch den Scheduler und das SPANK-Plugin verwaltet. In einer interaktiven „ Python “-Sitzung, bei der direkte Low-Level-API-Objekte verwendet werden, `release()` können und `acquire()` zur expliziten Verwaltung dedizierter Sitzungen genutzt werden.\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",
        "### HPC-Kontext: Slurm-Ressourcenzuweisung\n",
        "\n",
        "In einem HPC-Cluster beantragen Nutzer Quantenressourcen mithilfe der Slurm-GRES-Syntax in Verbindung mit den Optionen des QRMI-SPANK-Plugins. Das Plugin übernimmt die Einbindung von Anmeldedaten und Ressourcen automatisch:\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",
        "Der Anwendungscode erkennt die ihm zugewiesenen Ressourcen zur Laufzeit – es gibt keine fest codierten Backend-Namen:\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",
        "## Beispiel für groß angelegte Hardware: SQD auf N $_2$\n",
        "\n",
        "Hier fügen wir alle Komponenten zu einem vollständigen quantenchemischen Workflow in größerem Maßstab zusammen, der über QRMI auf echter „ IBM Quantum “-Hardware ausgeführt wird.\n",
        "\n",
        "**SQD** vereint Folgendes:\n",
        "\n",
        "1. Quantenabtastung eines „Local Unitary Cluster Jastrow“ (LUCJ)-Ansatzes, der unter Verwendung von … konstruiert `ffsim` und anhand von CCSD-Amplituden initialisiert wurde\n",
        "2. Hardware-orientierte Transpilation, die der „Heavy-Hex“-Gittertopologie entspricht, über `generate_lucj_pass_manager`\n",
        "3. Ausführung von Abtastvorgängen auf „ IBM Quantum “-Hardware, die über und `QRMIService` QRMI verwaltet wird `SamplerV2`\n",
        "4. Klassische Nachbearbeitung: selbstkonsistente Konfigurationswiederherstellung und iterative Subraumdiagonalisierung unter Verwendung von `qiskit-addon-sqd`\n",
        "\n",
        "Wir wenden SQD auf N- $_2$ bei einem Bindungsabstand von 1.0 $\\AA$ an, wobei der aktive Raum aus dem `cc-pVDZ` Basissatz abgeleitet wird (26 räumliche Orbitale, was 52 Spin-Orbitalen/Qubits entspricht).\n",
        "\n",
        "**Referenzenergie für N $_2$/cc-pVDZ -Aktivraum (Bindungsabstand 1.0 $\\AA$ ):**\n",
        "\n",
        "* Referenzenergie (separate SCI-Berechnung): **− 109.22802922 Ha**\n",
        "\n",
        "<Admonition type=\"note\" title=\"Genauigkeit des gespeicherten Durchlaufs\">\n",
        "  Der unten aufgeführte SQD-Lauf demonstriert die erfolgreiche End-to-End-Ausführung von QRMI auf einer „ IBM Quantum “-Hardware. Bei einer einzigen LUCJ-Wiederholung und 100.000 Iterationen liegt das Ergebnis etwa 23.7 kcal/mol über der Referenzenergie und erreicht keine chemische Genauigkeit (≤ 1 kcal/mol). Eine Änderung `n_reps` der Anzahl der Schüsse oder der Anzahl der SQD-Iterationen könnte die Genauigkeit verbessern, erfordert jedoch weitere Tests.\n",
        "</Admonition>\n",
        "\n",
        "In dem gespeicherten Durchlauf hat der `ffsim` Pass-Manager die Wechselwirkungen mit entgegengesetztem Spin entfernt `(24, 24)` , `(20, 20)` da das Backend diese nicht verarbeiten konnte. Die dargestellten Ergebnisse basieren auf dieser angepassten Schaltung.\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",
        "## Nächste Schritte\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Empfehlungen\">\n",
        "  Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an folgenden Materialien interessiert sein:\n",
        "\n",
        "  * [Tutorial zur samplebasierten Quantendiagonalisierung](/docs/tutorials/sample-based-quantum-diagonalization) – der vollständige SQD-Chemie-Workflow auf IBM Quantum Platform, einschließlich größerer Moleküle und Basissätze\n",
        "  * [Sample-basierte Krylov-Quanten-Diagonalisierung](/docs/tutorials/sample-based-krylov-quantum-diagonalization) – ein verwandtes Verfahren unter Verwendung von Zeitentwicklungsschaltungen für fermionische Gittermodelle\n",
        "  * [`qiskit-addon-sqd` Dokumentation](/docs/addons/qiskit-addon-sqd) – vollständige API-Referenz und weitere Tutorials zur SQD-Nachbearbeitungsbibliothek\n",
        "  * [QRMI- GitHub -Repository](https://github.com/qiskit-community/qrmi) – Quellcode, weitere Backend-Beispiele (CUDA-Q, C, Lua)\n",
        "  * [QRMI-Übersichtsdokument](https://arxiv.org/abs/2506.10052) – Technische Beschreibung der QRMI-Architektur und der HPC-Integration\n",
        "  * [IBM Quantum Compute Leitfaden zu Service-Sitzungen](/docs/guides/run-jobs-session) – Zusammenhang zwischen Sitzungen und dem QRMI bzw `acquire`.`release` dem Lebenszyklus für „ IBM “-Backends\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
}