{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"궤도 최적화를 통한 SQD 추정치 개선\"\n",
        "description: \"최신 버전의 샘플 기반 양자 대각화(SQD)에 대한 궤도 최적화를 통해 SQD 추정값 개선\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb5a576d",
      "metadata": {},
      "source": [
        "<span id=\"improve-an-sqd-estimate-with-orbital-optimization\" />\n",
        "\n",
        "# 궤도 최적화를 통한 SQD 추정치 개선\n",
        "\n",
        "샘플 기반 양자 대각화(SQD)는 전자 배열의 고정된 부분공간에서 해밀토니안을 대각화함으로써\n",
        "기저 상태 에너지를 근사화한다. 이러한\n",
        "추정치는 해밀토니안이 표현되는 궤도 기저에 따라 달라지며,\n",
        "*궤도 최적화* (OO)는 이러한 자유도를 활용하여 부분공간을 확대하지 않고도\n",
        "에너지를 낮추는 방식이다.\n",
        "\n",
        "이 가이드에서는 $N_2$ 분자에 대해 SQD를 실행한 다음, 궤도\n",
        "최적화를 통해 결과를 개선합니다. 이때 [`ffsim`](https://qiskit-community.github.io/ffsim/) 를 사용하여\n",
        "해밀토니안을 표현하고, 에너지를 최소화하는 궤도 회전 방향을 구합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "77ab953b",
      "metadata": {},
      "source": [
        "<span id=\"run-sqd\" />\n",
        "\n",
        "## SQD 실행\n",
        "\n",
        "우리는 분자 궤도(MO) 기저에서 $N_2$ 에 대한 분자 적분을 구축하고,\n",
        "균일한 무작위 표본을 생성한 다음, SQD를 실행하여 기저 상태 근사치를 구합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "b8d5618e",
      "metadata": {
        "execution": {
          "iopub.execute_input": "2026-07-16T01:36:48.334261Z",
          "iopub.status.busy": "2026-07-16T01:36:48.334063Z",
          "iopub.status.idle": "2026-07-16T01:37:42.585853Z",
          "shell.execute_reply": "2026-07-16T01:37:42.584303Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "converged SCF energy = -108.835236570775\n",
            "CASCI E = -109.046671778080  E(CI) = -32.8155692383187  S^2 = 0.0000000\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "import pyscf\n",
        "import pyscf.cc\n",
        "import pyscf.mcscf\n",
        "from qiskit_addon_sqd.counts import generate_bit_array_uniform\n",
        "from qiskit_addon_sqd.fermion import diagonalize_fermionic_hamiltonian\n",
        "\n",
        "# Specify molecule properties\n",
        "num_orbitals = 16\n",
        "num_elec_a = num_elec_b = 5\n",
        "spin_sq = 0\n",
        "\n",
        "# Build N2 molecule\n",
        "mol = pyscf.gto.Mole()\n",
        "mol.build(\n",
        "    atom=[[\"N\", (0, 0, 0)], [\"N\", (1.0, 0, 0)]],\n",
        "    basis=\"6-31g\",\n",
        "    symmetry=\"Dooh\",\n",
        ")\n",
        "\n",
        "# Define active space\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",
        "num_orbitals = len(active_space)\n",
        "n_electrons = int(sum(scf.mo_occ[active_space]))\n",
        "num_elec_a = (n_electrons + mol.spin) // 2\n",
        "num_elec_b = (n_electrons - mol.spin) // 2\n",
        "cas = pyscf.mcscf.CASCI(scf, num_orbitals, (num_elec_a, num_elec_b))\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), num_orbitals)\n",
        "\n",
        "# Compute exact energy\n",
        "exact_energy = cas.run().e_tot\n",
        "\n",
        "# Create a seed to control randomness throughout this workflow\n",
        "rng = np.random.default_rng(24)\n",
        "\n",
        "\n",
        "# Generate random samples\n",
        "bit_array = generate_bit_array_uniform(\n",
        "    10_000, num_orbitals * 2, rand_seed=rng\n",
        ")\n",
        "\n",
        "# Run SQD\n",
        "result = diagonalize_fermionic_hamiltonian(\n",
        "    hcore,\n",
        "    eri,\n",
        "    bit_array,\n",
        "    samples_per_batch=100,\n",
        "    norb=num_orbitals,\n",
        "    nelec=(num_elec_a, num_elec_b),\n",
        "    num_batches=1,\n",
        "    max_iterations=5,\n",
        "    symmetrize_spin=True,\n",
        "    seed=rng,\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "0ca70028",
      "metadata": {
        "execution": {
          "iopub.execute_input": "2026-07-16T01:37:42.590790Z",
          "iopub.status.busy": "2026-07-16T01:37:42.589477Z",
          "iopub.status.idle": "2026-07-16T01:37:42.595563Z",
          "shell.execute_reply": "2026-07-16T01:37:42.595133Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Exact energy:  -109.04667178\n",
            "SQD energy:    -108.98469255\n"
          ]
        }
      ],
      "source": [
        "sqd_energy = result.energy + nuclear_repulsion_energy\n",
        "print(f\"Exact energy:  {exact_energy:.8f}\")\n",
        "print(f\"SQD energy:    {sqd_energy:.8f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9160d09c",
      "metadata": {},
      "source": [
        "<span id=\"optimize-the-orbitals\" />\n",
        "\n",
        "## 궤도를 최적화한다\n",
        "\n",
        "궤도 최적화는 변분 에너지를 낮추는 궤도 회전 방식을 찾는 과정입니다\n",
        "\n",
        "$$\n",
        "E = \\langle \\psi | \\mathcal{U}^\\dagger\\, H\\, \\mathcal{U} | \\psi \\rangle\n",
        "$$\n",
        "\n",
        "SQD 기저 상태 근사 $|\\psi\\rangle$ 의 경우, 궤도 회전은\n",
        "$N \\times N$ 의 유니터리 행렬 $\\mathbf{U}$ (여기서 $N$ 는 공간 궤도의 개수)로 지정되며, 이는\n",
        "연산자를 통해 다체 상태에 작용한다.\n",
        "\n",
        "$$\n",
        "\\mathcal{U} = \\exp\\left[\\sum_{pq, \\sigma} \\log(\\mathbf{U})_{pq}\\, a^\\dagger_{p\\sigma} a_{q\\sigma}\\right].\n",
        "$$\n",
        "\n",
        "`ffsim.optimize_orbitals` $\\mathbf{U}$ **행렬** 을 반환하며, 이를\n",
        "궤도 기저에 (를 통해 `hamiltonian.rotated`) 적용하는 것은 $\\mathcal{U}$ 를\n",
        "상태에 적용하는 것과 동일하다. 자세한 내용은\n",
        "[ffsim의 궤도-회전 설명을](https://qiskit-community.github.io/ffsim/explanations/orbital-rotation.html)\n",
        "참고하십시오.\n",
        "\n",
        "궤도를 회전시키면 부분공간에서 인식되는 해밀토니안이 변하므로, 에너지가 더 이상 개선되지 않을 때까지\n",
        "다음 두 단계를 번갈아 가며 수행합니다:\n",
        "\n",
        "1. 고정된 구성 집합에 대해 현재 기저에서\n",
        "   해밀토니안을 **대각화하라**.\n",
        "2. 결과 상태의 에너지를 최소화하는 회전 각도를 구하여 **궤도를 최적화한** 다음,\n",
        "   적분식을 새로운 기저로 변환한다.\n",
        "\n",
        "우리는 궤도 회전 단계를 다음으로 위임하며\n",
        "[`ffsim.optimize_orbitals`](https://qiskit-community.github.io/ffsim/api/ffsim.html#ffsim.optimize_orbitals),\n",
        "이 단계에서는 상태의 1체 및 2체 환원 밀도 행렬(RDM)을 바탕으로\n",
        "에너지를 최소화하는 회전 각도를 구합니다. 해당 조항 참조\n",
        "[. 자세한 내용은 II A 4를](https://arxiv.org/pdf/2405.05068) 참조하십시오.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "006745b5",
      "metadata": {},
      "source": [
        "<span id=\"why-orbital-optimization-helps-here\" />\n",
        "\n",
        "### 이 경우 궤도 최적화가 도움이 되는 이유\n",
        "\n",
        "SCF 분자 궤도(MO) 기저 함수는 *전체* CI 문제에 대해 궤도 회전에 대해 정적\n",
        "입니다. 그러나 SQD는 작은 절단 부분공간(여기서는 약 1,900만 개의 전체 CI 결정자 중\n",
        "수백 개의 CI 문자열)에서 작동하는데, 이 부분공간에 대해서는 MO\n",
        "기저가 일반적으로 최적이 아니기 때문에, 궤도를 회전시키면 해당 부분공간이\n",
        "표현할 수 있는 에너지가 낮아진다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "46eb2f5a",
      "metadata": {
        "execution": {
          "iopub.execute_input": "2026-07-16T01:37:42.597573Z",
          "iopub.status.busy": "2026-07-16T01:37:42.597399Z",
          "iopub.status.idle": "2026-07-16T01:37:42.654809Z",
          "shell.execute_reply": "2026-07-16T01:37:42.654267Z"
        }
      },
      "outputs": [],
      "source": [
        "import ffsim\n",
        "from pyscf import fci\n",
        "\n",
        "# ffsim's ``MolecularHamiltonian`` uses the same \"chemist\" ordering for the two-body\n",
        "# tensor as PySCF's ``eri``, and stores the nuclear repulsion energy as the constant\n",
        "# term so that expectation values come out as total energies."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "93179dc4",
      "metadata": {},
      "source": [
        "<span id=\"alternate-diagonalization-and-orbital-optimization\" />\n",
        "\n",
        "### 교대 대각화와 궤도 최적화\n",
        "\n",
        "우리는 대각화 부분공간을 앞서 SQD를 통해 발견된 구성으로 **고정** 하여,\n",
        "각 반복 과정에서 궤도 회전 효과만을 분리하여 분석할 수 있도록 합니다. 각\n",
        "반복마다:\n",
        "\n",
        "1. PySCF's 의 선택된 CI(Selected-CI) 솔버를 사용하여,\n",
        "   현재 기저에서 고정된 부분공간에 대해 해밀토니안을 **대각화합니다**.\n",
        "2. 결과 상태의 **RDM을 생성하며**\n",
        "   , 이것이`ffsim.optimize_orbitals`\n",
        "   필요한 전부입니다.\n",
        "3. **궤도를 최적화합니다** : 에너지를 최소화하는\n",
        "   회전 각도를 반환하며`ffsim.optimize_orbitals`, 이를 적분에 적용하여 개선된 기저로 전환합니다.\n",
        "\n",
        "우리는 각 최적화 단계에 *앞서* 에너지 값을 기록합니다. 기저가 매\n",
        "반복마다 개선되기 때문에, 이 수열은 고정된 부분공간에서 달성 가능한 최상의 에너지 값을 향해\n",
        "단조 감소합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "a0783e88",
      "metadata": {
        "execution": {
          "iopub.execute_input": "2026-07-16T01:37:42.657394Z",
          "iopub.status.busy": "2026-07-16T01:37:42.657213Z",
          "iopub.status.idle": "2026-07-16T01:38:48.374706Z",
          "shell.execute_reply": "2026-07-16T01:38:48.373697Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Iteration 0: energy = -108.98452447\n",
            "Iteration 1: energy = -108.99981993\n",
            "Iteration 2: energy = -109.00585329\n",
            "Iteration 3: energy = -109.00816569\n",
            "Iteration 4: energy = -109.00936616\n",
            "Iteration 5: energy = -109.01014322\n",
            "Iteration 6: energy = -109.01069439\n",
            "Iteration 7: energy = -109.01109308\n",
            "Iteration 8: energy = -109.01138928\n",
            "Iteration 9: energy = -109.01161411\n"
          ]
        }
      ],
      "source": [
        "# Fix the diagonalization subspace to the configurations found by SQD.\n",
        "ci_strings = (result.sci_state.ci_strs_a, result.sci_state.ci_strs_b)\n",
        "nelec = (num_elec_a, num_elec_b)\n",
        "\n",
        "# Start from the MO basis in which we ran SQD.\n",
        "hamiltonian_opt = ffsim.MolecularHamiltonian(\n",
        "    hcore, eri, constant=nuclear_repulsion_energy\n",
        ")\n",
        "\n",
        "num_iters = 10\n",
        "for i in range(num_iters):\n",
        "    # Diagonalize over the fixed subspace in the current basis.\n",
        "    myci = fci.selected_ci.SelectedCI()\n",
        "    myci = fci.addons.fix_spin_(myci, ss=spin_sq)\n",
        "    _, amplitudes = fci.selected_ci.kernel_fixed_space(\n",
        "        myci,\n",
        "        hamiltonian_opt.one_body_tensor,\n",
        "        hamiltonian_opt.two_body_tensor,\n",
        "        num_orbitals,\n",
        "        nelec,\n",
        "        ci_strs=ci_strings,\n",
        "    )\n",
        "\n",
        "    # Build the RDMs and record the energy before re-optimizing the orbitals.\n",
        "    dm1, dm2 = myci.make_rdm12(amplitudes, num_orbitals, nelec)\n",
        "    rdm = ffsim.ReducedDensityMatrix(dm1, dm2)\n",
        "    energy = rdm.expectation(hamiltonian_opt).real\n",
        "    print(f\"Iteration {i}: energy = {energy:.8f}\")\n",
        "\n",
        "    # Rotate the Hamiltonian into the energy-minimizing basis for the next iteration.\n",
        "    # optimize_orbitals returns the unitary matrix U minimizing\n",
        "    # rdm.rotated(U).expectation(hamiltonian), equivalently\n",
        "    # rdm.expectation(hamiltonian.rotated(U.conj().T)), so we rotate by U^dagger.\n",
        "    orbital_rotation = ffsim.optimize_orbitals(rdm, hamiltonian_opt)\n",
        "    hamiltonian_opt = hamiltonian_opt.rotated(orbital_rotation.T.conj())"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a61dbda8",
      "metadata": {},
      "source": [
        "<span id=\"compare-the-results\" />\n",
        "\n",
        "### 결과를 비교해 보세요\n",
        "\n",
        "궤도 최적화는 고정 부분공간 추정치를 개선하여, 정확한 에너지 값과의 격차를 상당 부분 좁히면서도\n",
        "그 값보다 높은 수준을 유지합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "762b0903",
      "metadata": {
        "execution": {
          "iopub.execute_input": "2026-07-16T01:38:48.377620Z",
          "iopub.status.busy": "2026-07-16T01:38:48.377404Z",
          "iopub.status.idle": "2026-07-16T01:38:53.111168Z",
          "shell.execute_reply": "2026-07-16T01:38:53.110312Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Exact energy:      -109.04667178\n",
            "SQD energy (MO):   -108.98469255\n",
            "Energy after OO:   -109.01178727\n"
          ]
        }
      ],
      "source": [
        "# Diagonalize once more in the final optimized basis to report the improved energy.\n",
        "myci = fci.selected_ci.SelectedCI()\n",
        "myci = fci.addons.fix_spin_(myci, ss=spin_sq)\n",
        "_, amplitudes = fci.selected_ci.kernel_fixed_space(\n",
        "    myci,\n",
        "    hamiltonian_opt.one_body_tensor,\n",
        "    hamiltonian_opt.two_body_tensor,\n",
        "    num_orbitals,\n",
        "    nelec,\n",
        "    ci_strs=ci_strings,\n",
        ")\n",
        "dm1, dm2 = myci.make_rdm12(amplitudes, num_orbitals, nelec)\n",
        "energy_after_oo = (\n",
        "    ffsim.ReducedDensityMatrix(dm1, dm2).expectation(hamiltonian_opt).real\n",
        ")\n",
        "\n",
        "print(f\"Exact energy:      {exact_energy:.8f}\")\n",
        "print(f\"SQD energy (MO):   {sqd_energy:.8f}\")\n",
        "print(f\"Energy after OO:   {energy_after_oo:.8f}\")"
      ]
    },
    {
      "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
}