{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"빠른 시작\"\n",
        "description: \"최신 버전의 샘플 기반 양자 대각화(SQD) 빠른 시작 가이드\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "intro",
      "metadata": {},
      "source": [
        "<span id=\"quickstart\" />\n",
        "\n",
        "# 빠른 시작\n",
        "\n",
        "이 가이드에서는 해당 `qiskit-addon-sqd` 패키지의 최소 실행 가능한 예제를 보여줍니다. 우리는 화학 해밀토니안의 기저 상태를 근사하는 양자 회로에서 추출한 잡음이 포함된 샘플을 정제하기 위해 샘플 기반 양자 대각화(SQD)를 사용합니다. 기저 상태 에너지는 정제된 표본들로 구성된 부분공간에서 해밀토니안을 대각화함으로써 근사화된다.\n",
        "\n",
        "양자 하드웨어에서 실행되는 엔드투엔드 워크플로우에 대한 자세한 설명은 다음 튜토리얼을 참고하세요:\n",
        "\n",
        "* [샘플 기반 양자 대각화](/docs/tutorials/sample-based-quantum-diagonalization) : 화학 해밀토니안 워크플로우\n",
        "* [샘플 기반 크릴로프 양자 대각화](/docs/tutorials/sample-based-krylov-quantum-diagonalization) : 페르미온 격자 모델 워크플로우\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "prepare-header",
      "metadata": {},
      "source": [
        "<span id=\"1-prepare-the-inputs-for-sqd\" />\n",
        "\n",
        "## 1. SQD에 필요한 입력 데이터를 준비합니다\n",
        "\n",
        "먼저, 화학 해밀토니안과 그 기저 상태를 근사하는 양자 회로를 정의합니다. 이 예제에서 연구한 시스템은 두 개의 코어 궤도가 고정된 STO-3G 기저에서 평형 기하 구조를 갖는 N₂입니다. 이 시스템에는 개의 공간 궤도와 `10` 개의 전자가 있으며 `8` , 이는 양자 회로가 개의 `16` 큐비트로 정의됨을 의미합니다. 여기서 큐비트 `0-7` 는 스핀-업 궤도를, 큐비트 `8-15` 는 스핀-다운 궤도를 나타냅니다. `5``10`이 시스템은 개의 전자를 가진 `10` 폐쇄 껍질 시스템이므로, 물리적으로 타당한 각 샘플의 해밍 가중치는 여야 함을 알 수 있으며, 이때 비트열의 좌우 양쪽 절반 모두의 해밍 가중치는 이다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "load-molecule",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Spatial orbitals: 8\n",
            "Qubits: 16\n",
            "Electrons (alpha, beta): (5, 5)\n"
          ]
        }
      ],
      "source": [
        "from pyscf import ao2mo, gto, mcscf, scf\n",
        "\n",
        "# Construct the Hamiltonian, pull out integrals and other information about the system\n",
        "mol = gto.M(\n",
        "    atom=\"N 0 0 0; N 0 0 1.09768\", basis=\"sto-3g\", spin=0, charge=0, verbose=0\n",
        ")\n",
        "mf = scf.RHF(mol).run()\n",
        "cas = mcscf.CASCI(mf, ncas=8, nelecas=10)\n",
        "hcore, nuclear_repulsion_energy = cas.get_h1eff()\n",
        "eri = cas.get_h2eff()\n",
        "num_orbitals = cas.ncas\n",
        "eri = ao2mo.restore(1, eri, num_orbitals)\n",
        "n_alpha, n_beta = cas.nelecas\n",
        "nelec = (n_alpha, n_beta)\n",
        "\n",
        "print(\n",
        "    f\"Spatial orbitals: {num_orbitals}\\nQubits: {num_orbitals * 2}\\nElectrons (alpha, beta): {nelec}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "samples-explanation",
      "metadata": {},
      "source": [
        "<span id=\"2-generate-noisy-samples\" />\n",
        "\n",
        "## 2. 노이즈가 포함된 샘플 생성하기\n",
        "\n",
        "실제 워크플로우에서는 안자츠 회로를 설계하고, 하드웨어에 맞게 최적화한 다음, QPU에서 샘플링하게 됩니다. 여기서는 그 과정을 생략하고, 대신 균일하게 무작위 비트열을 생성합니다 `10,000` . 비록 우리의 샘플에는 실제 기저 상태에 대한 정보가 포함되어 있지 않지만, SQD 프로토콜이 이러한 샘플을 반복적으로 정제하고 대각화한 뒤, 각 반복 단계에서 구한 에너지를 다음 단계의 비트열 정제에 반영함으로써 여전히 진정한 기저 상태를 복원할 수 있음을 보게 될 것입니다. 기저 상태를 포함하는 부분공간이 충분히 작아서 정확히 계산할 수 있기 때문에, 우리는 결국 이러한 방식으로 정확한 기저 상태를 복원할 수 있다. `5`구체적으로, 각 비트열(`8` bits)의 각 반쪽이 해밍 가중치 를 가져야 한다는 사실을 알고 있으므로, 이 시스템의 전체 힐베르트 부분공간은 크기 $\\binom{8}{5}^2=3136$ 임을 알 수 있다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "generate-samples",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Generated 10000 uniformly-random, 16-qubit samples.\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "from qiskit_addon_sqd.counts import generate_bit_array_uniform\n",
        "\n",
        "# Generate some uniformly-random bitstrings to simulate QPU samples\n",
        "rng = np.random.default_rng(24)\n",
        "bit_array = generate_bit_array_uniform(\n",
        "    10_000, num_orbitals * 2, rand_seed=rng\n",
        ")\n",
        "print(\n",
        "    f\"Generated {bit_array.num_shots} uniformly-random, {bit_array.num_bits}-qubit samples.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "postprocess-header",
      "metadata": {},
      "source": [
        "<span id=\"3-post-process-with-sqd\" />\n",
        "\n",
        "## 3. SQD를 이용한 후처리\n",
        "\n",
        "SQD 알고리즘의 목적은 QPU에서 추출한 잡음이 섞인 샘플을 정제하고, 궁극적으로 해당 정제된 샘플들이 생성하는 부분공간에서 해밀토니안을 대각화하는 데 있습니다. 이는 다음과 같이 반복적으로 수행됩니다:\n",
        "\n",
        "* 스핀-업 및 스핀-다운 하위 시스템 모두에서 올바른 입자 수를 가진 샘플만을 사후 선택하여 샘플을 정제합니다\n",
        "* 정제된 샘플들이 생성하는 작은 부분공간에서 대각화를 수행하고, 가장 좋은(가장 낮은) 에너지를 나타내는 부분공간에 대한 정보를 수집한다.\n",
        "* 반복될 때마다,\n",
        "  * 입자 수가 잘못된 샘플의 비트를 확률적으로 뒤집어 노이즈가 많은 샘플을 정제합니다. 확률은 가장 최근의 대각화 과정에서 얻은 부분공간 정보를 사용하여 계산됩니다.\n",
        "  * 정제된 표본들이 생성하는 작은 부분공간에서 대각화하기\n",
        "  * 최적(최저) 에너지를 갖는 부분공간에 대한 정보를 수집한다\n",
        "\n",
        "출력 결과를 통해, 알고리즘이 회 반복 후 `6` 정확한 기저 상태 에너지로 수렴하며, 차원 $\\binom{8}{5}^2=3136$ 인 전체 힐베르트 부분공간에 대해 대각화가 수행됨을 알 수 있다. 물론, 전체 부분공간에 대한 대각화는 일반적으로 실현하기 어려우므로, 기저 상태의 지지 영역이 드문 시스템일수록 SQD와 같은 부분공간 대각화 기법의 이점을 가장 크게 누릴 가능성이 높다.\n",
        "\n",
        "diagonalize\\_fermionic\\_hamiltonian 함수에 대한 자세한 내용은 [API](/docs/api/qiskit-addon-sqd/fermion#diagonalize_fermionic_hamiltonian) 문서를 참조하십시오.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "run-sqd",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Exact (FCI) reference energy: -107.652521 Ha\n",
            "\n",
            "Iteration 1\n",
            "  Subsample 0\n",
            "    Energy: -106.908481\n",
            "    Subspace dimension: 784\n",
            "    Error vs exact: 0.744040 Ha\n",
            "Iteration 2\n",
            "  Subsample 0\n",
            "    Energy: -106.934921\n",
            "    Subspace dimension: 1764\n",
            "    Error vs exact: 0.717600 Ha\n",
            "Iteration 3\n",
            "  Subsample 0\n",
            "    Energy: -106.953430\n",
            "    Subspace dimension: 2209\n",
            "    Error vs exact: 0.699091 Ha\n",
            "Iteration 4\n",
            "  Subsample 0\n",
            "    Energy: -107.651946\n",
            "    Subspace dimension: 2916\n",
            "    Error vs exact: 0.000575 Ha\n",
            "Iteration 5\n",
            "  Subsample 0\n",
            "    Energy: -107.652003\n",
            "    Subspace dimension: 3025\n",
            "    Error vs exact: 0.000518 Ha\n",
            "Iteration 6\n",
            "  Subsample 0\n",
            "    Energy: -107.652521\n",
            "    Subspace dimension: 3136\n",
            "    Error vs exact: 0.000000 Ha\n",
            "Iteration 7\n",
            "  Subsample 0\n",
            "    Energy: -107.652521\n",
            "    Subspace dimension: 3136\n",
            "    Error vs exact: 0.000000 Ha\n"
          ]
        }
      ],
      "source": [
        "from functools import partial\n",
        "\n",
        "from pyscf import fci\n",
        "from qiskit_addon_sqd.fermion import (\n",
        "    SCIResult,\n",
        "    diagonalize_fermionic_hamiltonian,\n",
        "    solve_sci_batch,\n",
        ")\n",
        "\n",
        "exact_energy, _ = fci.direct_spin1.kernel(\n",
        "    hcore, eri, int(num_orbitals), nelec\n",
        ")\n",
        "exact_energy += nuclear_repulsion_energy\n",
        "print(f\"Exact (FCI) reference energy: {exact_energy:.6f} Ha\\n\")\n",
        "\n",
        "sci_solver = partial(solve_sci_batch, spin_sq=0.0, max_cycle=200)\n",
        "\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, result in enumerate(results):\n",
        "        error = result.energy + nuclear_repulsion_energy - exact_energy\n",
        "        print(f\"  Subsample {i}\")\n",
        "        print(f\"    Energy: {result.energy + nuclear_repulsion_energy:.6f}\")\n",
        "        print(\n",
        "            f\"    Subspace dimension: {np.prod(result.sci_state.amplitudes.shape)}\"\n",
        "        )\n",
        "        print(f\"    Error vs exact: {error:.6f} Ha\")\n",
        "\n",
        "\n",
        "samples_per_batch = 20\n",
        "result = diagonalize_fermionic_hamiltonian(\n",
        "    hcore,\n",
        "    eri,\n",
        "    bit_array,\n",
        "    samples_per_batch=samples_per_batch,\n",
        "    norb=num_orbitals,\n",
        "    nelec=nelec,\n",
        "    occupancies_tol=1e-7,\n",
        "    max_iterations=30,\n",
        "    sci_solver=sci_solver,\n",
        "    symmetrize_spin=True,\n",
        "    callback=callback,\n",
        "    seed=np.random.default_rng(32),\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
}