{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "title",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"QRMI를 사용하여 양자 워크로드를 실행하세요\"\n",
        "description: \"Quantum Resource Management Interface를 사용하여 IBM Quantum 워크로드를 관리하고 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",
        "# QRMI를 사용하여 양자 워크로드를 실행하세요\n",
        "\n",
        "예상 *소요 시간: IBM Quantum® 하드웨어에서 SQD 섹션 처리 시 1분 미만. 이 예상 시간에는 대기 시간과 일반적인 처리 시간은 포함되지 않으며, 실제 소요 시간은 달라질 수 있습니다.*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 학습 성과\n",
        "\n",
        "1. HPC 스케줄러와 IBM Quantum 하드웨어 사이에서 QRMI가 수행하는 미들웨어 역할\n",
        "2. 실제 IBM® 백엔드에서 핵심 QRMI 라이프사이클(`acquire` → `task_start` → `task_status` → → `task_result` `release`)을 사용하는 방법\n",
        "3. QRMI를 기반으로 한 상위 레벨 Qiskit 및 `SamplerV2` `QRMIService` 래퍼를 사용하는 방법\n",
        "4. HPC 스케줄러(Slurm)가 환경 변수를 통해 양자 자원을 할당하는 방식과 애플리케이션이 이를 활용하는 방식\n",
        "5. QRMI를 통해 IBM 하드웨어를 활용하여 N $_2$ 에서 완전한 SQD(샘플 기반 양자 대각화) 화학 워크플로를 실행하는 방법\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 전제조건\n",
        "\n",
        "* [Qiskit primitives (표본 추출기 및 추정기)](/docs/guides/primitives)\n",
        "* [IBM Quantum 세션](/docs/guides/run-jobs-session)\n",
        "* [IBM Quantum 전사](/docs/guides/transpile)\n",
        "* [샘플 기반 양자 대각화(SQD)](/docs/tutorials/sample-based-quantum-diagonalization)\n",
        "* Python 가상 환경 및 양자 화학에 대한 기본적인 이해\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 배경\n",
        "\n",
        "<span id=\"the-quantum-hpc-integration-challenge\" />\n",
        "\n",
        "### 양자-HPC 통합의 과제\n",
        "\n",
        "고성능 컴퓨팅(HPC) 워크플로에서는 종종 기존 컴퓨팅 클러스터와 양자 처리 장치(QPU) 간의 원활한 연동이 필요합니다. 각기 다른 양자 하드웨어 백엔드와 서비스는 서로 다른 인증 메커니즘, 전송 형식 및 작업 수명 주기 API를 제공합니다. IBM Quantum 시스템을 HPC 워크로드 관리자(예: Slurm)에 통합하려면 리소스 확보, 작업 실행 및 세션 관리를 위한 명확하고 표준화된 인터페이스가 필요합니다.\n",
        "\n",
        "<span id=\"what-qrmi-is\" />\n",
        "\n",
        "### QRMI란 무엇인가\n",
        "\n",
        "**양자 자원 관리 인터페이스(QRMI)** 는 Rust로 작성된 미들웨어 라이브러리로, HPC 스케줄러와 고전적 애플리케이션에서 양자 하드웨어에 대한 접근을 표준화합니다. 단일 통합 라이프사이클 API를 제공합니다:\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는 [github.com/qiskit-community/qrmi](https://github.com/qiskit-community/qrmi) 에서 오픈소스 프로젝트로 공개되어 있으며, 개요 논문 [arXiv:2506.10052](https://arxiv.org/abs/2506.10052) 에 자세히 설명되어 있습니다.\n",
        "\n",
        "<span id=\"key-design-choices\" />\n",
        "\n",
        "### 주요 설계 결정 사항\n",
        "\n",
        "**회로 컴파일링이 아니라 리소스 수명 주기입니다.** QRMI는 획득/제출/폴링/해제 라이프사이클만을 처리하며, 그 외의 기능은 처리하지 않습니다. 회로 컴파일, 최적화 및 트랜스파일링은 여전히 애플리케이션 계층(예: Qiskit)에서 수행됩니다. 이를 통해 인터페이스를 간결하고 조합 가능하게 유지합니다.\n",
        "\n",
        "**공급업체 이동성 모델.** QRMI는 지원되는 하드웨어 백엔드 전반에 걸쳐 공통적인 작업 관리 호출(`acquire`, `task_start`, `task_status`, `task_result`, `release`)을 제공하지만, 벤더를 변경할 경우 애플리케이션 계층에서 서로 다른 컴파일 단계, 벤더별 페이로드 구성 및 결과 디코딩이 필요합니다.\n",
        "\n",
        "**IBM 의 기본 페이로드 형식.** IBM Quantum 백엔드의 경우, QRMI는 Qiskit Runtime 스키마를 준수하는 OpenQASM 3개의 JSON 페이로드(`QiskitPrimitive`)를 사용합니다.\n",
        "\n",
        "**환경 변수를 통한 구성.** 인증 정보와 엔드포인트 URL은 실행 시 환경 변수에서 읽어옵니다. HPC 클러스터에서는 작업이 할당될 때 Slurm QRMI SPANK 플러그인이 이러한 설정을 자동으로 지정합니다. 노트북이나 대화형 세션에서는 `.env` 파일에서 이를 불러옵니다. 애플리케이션 코드에는 절대 하드코딩된 인증 정보나 엔드포인트 URL이 포함되지 않습니다.\n",
        "\n",
        "**GRES를 통한 HPC 스케줄러 통합.** Slurm 작업이 QRMI SPANK 플러그인 인터페이스(`#SBATCH --gres=qpu:1` 및 `#SBATCH --qpu=ibm_kingston`)를 사용하여 퀀텀 리소스를 요청하면, 플러그인은 `QRMI_JOB_QPU_RESOURCES` 및 를 작업 환경에 `QRMI_JOB_QPU_TYPES` 삽입합니다. 애플리케이션은 호출을 통해 `get_job_qpu_resources_and_types()` 어떤 리소스가 할당되었는지 확인할 수 있으며, 백엔드 이름을 하드코딩할 필요가 없습니다. `QRMIService` Qiskit 사용자를 위해 이 패턴을 래핑합니다.\n",
        "\n",
        "<span id=\"the-core-api-calls\" />\n",
        "\n",
        "### 핵심 API 호출\n",
        "\n",
        "| 호출                         | 용도                                                    |\n",
        "| -------------------------- | ----------------------------------------------------- |\n",
        "| `qrmi.acquire()`           | 리소스에 대한 액세스 권한을 획득합니다(예: 전용 세션을 엽니다). 잠금 토큰을 반환합니다    |\n",
        "| `qrmi.target()`            | 백엔드 기능(큐비트, 게이트, 커플링 맵)을 JSON 형식으로 가져오기               |\n",
        "| `qrmi.task_start(payload)` | 양자 작업을 제출합니다. 작업 ID를 반환합니다                            |\n",
        "| `qrmi.task_status(job_id)` | 작업 상태 조회 (`Queued`, `Running`, `Completed`, `Failed`) |\n",
        "| `qrmi.task_result(job_id)` | 완료된 작업 결과를 원시 JSON 문자열로 가져오기                          |\n",
        "| `qrmi.task_stop(job_id)`   | 작업 취소 또는 정리                                           |\n",
        "| `qrmi.release(lock)`       | 리소스 잠금을 해제합니다(예: 세션을 종료합니다)                           |\n",
        "\n",
        "<span id=\"what-this-tutorial-covers\" />\n",
        "\n",
        "### 이 튜토리얼에서 다루는 내용\n",
        "\n",
        "이 튜토리얼은 두 부분으로 구성되어 있습니다:\n",
        "\n",
        "**1\\~3단계 (소규모 예시):** IBM Quantum 하드웨어에서 간단한 벨 상태 회로 시연을 통해 QRMI API를 소개하며, 직접적인 저수준 기본 기능 사용법뿐만 아니라 고수준 기능 `QRMIService` 및 `SamplerV2` 통합에 대해서도 다룹니다.\n",
        "\n",
        "**대규모 하드웨어 예시:** 결합 거리 1.0 $\\AA$ ( cc-pVDZ 기저 활성 공간, 26개의 공간 궤도 / 52개의 큐비트)에서 N $_2$ 분자에 대한 완전한 SQD 워크플로우로, QRMI를 통해 IBM Quantum 하드웨어에서 실행되었습니다. SQD는 로 구성된 LUCJ 안자츠에 대한 양자 샘플링과 `ffsim` 를 이용한 자기일관적 구성 복원을 결합한 것입니다 `qiskit-addon-sqd`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 요구사항\n",
        "\n",
        "이 튜토리얼을 시작하기 전에 다음 항목이 설치되어 있는지 확인하십시오.\n",
        "\n",
        "<span id=\"python-environment-setup\" />\n",
        "\n",
        "### Python 환경 설정\n",
        "\n",
        "PyPI, 에서 Linux 용 사전 빌드된 바이너리 휠을 제공하므로, 표준 버전은 Linux /HPC 시스템에서 바로 작동합니다 `pip install` .\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=\"사전 조립된 바퀴가 없는 플랫폼\">\n",
        "  소스 코드에서 QRMI를 `pip` 빌드하는 경우, 최신 Rust 툴체인(Rust ≥ 1.91.1 이 설치되어 있는지 확인하십시오. 이 툴체인은 [rustup.rs](https://rustup.rs)`rustup` 에서 다운로드하여 설치할 수 있습니다.)\n",
        "</Admonition>\n",
        "\n",
        "Jupyter에서 **QRMI IBM** 커널을 선택한 다음, 커널을 다시 시작한 후 노트북 셀을 순서대로 실행하십시오. 저장된 출력 결과는 기여자가 하드웨어에서 실행한 결과이며, 설치 명령어에는 해당 실행에 사용된 정확한 버전이 명시되어 있지 않습니다.\n",
        "\n",
        "<span id=\"credentials-required\" />\n",
        "\n",
        "### 신임 정보 필요\n",
        "\n",
        "* IBM Quantum : IAM API 키 및 서비스 CRN (출처: [IBM Quantum Platform]() )\n",
        "\n",
        "독립 실행을 하려면, 이 노트북 옆에 다음 값을 포함하는 파일을 `.env` 생성하고, 자격 증명 자리 표시자를 적절한 값으로 대체하십시오. 이 파일은 비공개로 유지해 주세요. 다른 백엔드를 선택하는 경우, 해당 백엔드의 이름과 환경 변수 접두사를 모두 업데이트하십시오.\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",
        "Slurm 할당 시에는 클러스터에서 제공한 리소스 설정 및 인증 정보를 사용하십시오. 이 노트북은 기존 환경 변수 값을 유지합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-header",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## 설정\n",
        "\n",
        "의존성을 가져오고 리소스 구성을 불러옵니다.\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",
        "## 소규모 사례\n",
        "\n",
        "1\\~3단계에서는 간단한 회로를 통해 QRMI API를 소개합니다. 각 단계는 IBM Quantum 하드웨어를 기준으로 한 QRMI 라이프사이클의 핵심 단계와 대응됩니다.\n",
        "\n",
        "이러한 초기 단계에 사용되는 페이로드는 실행 속도가 빠르고 비용이 저렴하도록 선택된 작은 벨 상태 회로입니다.\n",
        "\n",
        "이 예제들은 원격 리소스 할당 및 작업 관리를 보여주기 위해 하드웨어를 사용합니다. 로컬 회로 시뮬레이터는 QRMI 서비스와 스케줄러의 통합을 검증하지 않습니다. 이 노트북을 실행하면 IBM Quantum 작업이 제출되며, 구성된 백엔드에 대한 액세스 권한이 필요합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-the-classical-problem-to-a-quantum-resource\" />\n",
        "\n",
        "### 1단계: 고전적 문제를 양자 자원에 매핑하기\n",
        "\n",
        "모든 QRMI 워크플로우의 첫 번째 단계는 `QuantumResource` 객체를 생성하고, 해당 객체에 접근할 수 있는지 확인하는 것입니다.\n",
        "\n",
        "`get_target()` 백엔드의 하드웨어 설명(큐비트 수, 베이스 게이트, 커플링 맵)을 가져와 이를 Qiskit 객체로 패키징하며, 이 `Target` 객체는 2단계에서 트랜스파일러가 사용하게 됩니다.\n",
        "\n",
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### 2단계: 양자 하드웨어 실행을 위해 문제를 최적화한다\n",
        "\n",
        "제출하기 전에, 1단계에서 가져온 객체를 `Target` 사용하여 Qiskit을 통해 회로를 백엔드의 명령어 집합 아키텍처(ISA)로 변환하십시오.\n",
        "\n",
        "그런 다음 이 예제는 OpenQASM 의 3개 회로 문자열과 작업 메타데이터를 IBM 기본 스키마로 감싸는 객체를 생성합니다 `Payload.QiskitPrimitive`.\n",
        "\n",
        "<span id=\"step-3-execute-using-qrmi-primitives\" />\n",
        "\n",
        "### 3단계: QRMI 프리미티브를 사용하여 실행하기\n",
        "\n",
        "페이로드가 생성되면, 이 예제는 작업을 제출하고 완료 여부를 확인합니다. `task_start()` 즉시 작업 ID를 반환하며, 상태가 더 이상 `Queued`/가 아닐 때까지 폴링됩니다 `task_status()``Running` . 결과는 원시 JSON 문자열로 가져온 후, 이를 파싱하여 측정 샘플을 추출합니다.\n",
        "\n",
        "다음 셀은 획득, 실행 및 정리 과정을 하나로 묶어, 획득 후 오류가 발생하더라도 노트북 소유의 세션이 해제되도록 합니다.\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 인터페이스: QRMIService 및 SamplerV2\n",
        "\n",
        "위의 원시 라이프사이클을 사용하면 모든 호출을 명시적으로 제어할 수 있습니다. 표준 Qiskit 워크플로우의 경우, QRMI는 이를 구현하는 기본 `SamplerV2` 기능을 제공합니다 `BaseSamplerV2`.\n",
        "\n",
        "<Admonition type=\"note\" title=\"라이프사이클 관리\">\n",
        "  `SamplerV2` 페이로드 직렬화, 제출(`task_start`), 폴링 및 결과 디코딩을 처리합니다. HPC 배치 환경(예: Slurm)에서는 할당 및 해제가 스케줄러와 SPANK 플러그인에 의해 관리됩니다. 직접적인 저수준 API 객체를 사용하는 대화형 Python 세션에서는 및 `acquire()` 을 사용하여 전용 세션을 명시적으로 관리할 수 `release()` 있습니다.\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 관련: Slurm 리소스 주입\n",
        "\n",
        "HPC 클러스터에서 사용자는 Slurm GRES 구문과 QRMI SPANK 플러그인 옵션을 함께 사용하여 양자 자원을 요청합니다. 이 플러그인은 자격 증명 및 리소스 주입을 자동으로 처리합니다:\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",
        "애플리케이션 코드는 런타임에 할당된 리소스를 자동으로 파악하며, 백엔드 이름이 하드코딩되어 있지 않습니다:\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",
        "## 대규모 하드웨어 예시: N $_2$ 상의 SQD\n",
        "\n",
        "여기서는 모든 구성 요소를 통합하여 더 큰 규모의 완전한 양자화학 워크플로우를 구축하고, QRMI를 통해 실제 IBM Quantum 하드웨어에서 이를 실행합니다.\n",
        "\n",
        "**SQD는** 다음 요소들을 결합합니다:\n",
        "\n",
        "1. CCSD 진폭을 기반으로 구축되고 `ffsim` 초기화된 국소 단일 클러스터 Jastrow(LUCJ) 가설의 양자 샘플링\n",
        "2. 다음과 같은 방식을 통해 헤비-헥스 격자 토폴로지에 부합하는 하드웨어 인식 트랜스파일레이션 `generate_lucj_pass_manager`\n",
        "3. IBM Quantum 하드웨어에서, 및 `QRMIService` QRMI를 통해 관리되는 샘플링 실행 `SamplerV2`\n",
        "4. 전통적인 후처리: 다음을 이용한 자기일관적 구성 복원 및 반복적 부분공간 대각화 `qiskit-addon-sqd`\n",
        "\n",
        "우리는 결합 거리 1.0 $\\AA$ 에서 N $_2$ 에 SQD를 적용하며, 기저 `cc-pVDZ` 집합(26개의 공간 궤도, 52개의 스핀-궤도/큐비트에 해당)에서 도출된 활성 공간을 사용합니다.\n",
        "\n",
        "**N $_2$ /cc-pVDZ 활성 공간에 대한 기준 에너지 (결합 거리 1.0 $\\AA$ ):**\n",
        "\n",
        "* 기준 에너지 (별도의 SCI 계산): **− 109.22802922 Ha**\n",
        "\n",
        "<Admonition type=\"note\" title=\"저장된 실행 결과의 정확도\">\n",
        "  아래의 SQD 실행 결과는 ‘ IBM Quantum ’ 하드웨어에서 QRMI가 종단 간 성공적으로 실행되었음을 보여줍니다. LUCJ 반복을 한 번 수행하고 100,000회의 시뮬레이션을 실행한 결과, 계산값은 기준 에너지보다 약 23.7 kcal/mol 높게 나왔으며, 화학적 정확도(≤ 1 kcal/mol)를 달성하지 못했습니다. 샷 수나 `n_reps` SQD 반복 횟수를 변경하면 정확도가 향상될 수 있지만, 이에 대해서는 추가적인 테스트가 필요합니다.\n",
        "</Admonition>\n",
        "\n",
        "저장된 실행에서, 백엔드가 이를 처리할 수 없었기 때문에 `(20, 20)` 패스 `ffsim` 매니저가 반대 스핀 상호작용과 `(24, 24)` 를 제거했습니다. 보고된 결과는 이 조정된 회로를 사용한 것입니다.\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",
        "\n",
        "<Admonition type=\"tip\" title=\"권장사항\">\n",
        "  이 글이 흥미로웠다면, 다음 자료도 참고해 보시기 바랍니다:\n",
        "\n",
        "  * [샘플 기반 양자 대각화 튜토리얼](/docs/tutorials/sample-based-quantum-diagonalization) — 더 큰 분자와 기저 집합을 포함한 IBM Quantum Platform 의 전체 SQD 화학 워크플로우\n",
        "  * [샘플 기반 크릴로프 양자 대각화](/docs/tutorials/sample-based-krylov-quantum-diagonalization) — 페르미온 격자 모델에 시간 진화 회로를 활용하는 관련 방법\n",
        "  * [`qiskit-addon-sqd` 문서](/docs/addons/qiskit-addon-sqd) — SQD 후처리 라이브러리에 대한 전체 API 참조 및 추가 튜토리얼\n",
        "  * [QRMI GitHub 저장소](https://github.com/qiskit-community/qrmi) — 소스 코드, 추가 백엔드 예제 (CUDA-Q, C, Lua)\n",
        "  * [QRMI 개요 문서](https://arxiv.org/abs/2506.10052) — QRMI 아키텍처 및 HPC 통합에 대한 기술적 설명\n",
        "  * [IBM Quantum Compute 서비스 세션 가이드](/docs/guides/run-jobs-session) — 세션이 QRMI `acquire`및`release` 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
}