{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"AQC + 트로터 동역학을 활용한 중성자 산란 시뮬레이션 서버리스 워크플로우\"\n",
        "description: \"AQC 압축을 배포된 함수 템플릿으로 설정하여 Trotter 워크플로를 실행함으로써, 양자 자석 KCuF3 의 동적 구조 인자 S(q, w)를 계산합니다.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore Trotter Trotterization spinon spinons KCuF quimb DMRG magnon antiferromagnetic Suzuki fidelities isa COBYQA Gjjc qpoints viridis fontsize vmax vmin */}\n",
        "\n",
        "<span id=\"simulate-neutron-scattering-with-an-aqc-+-trotter-dynamics-serverless-workflow\" />\n",
        "\n",
        "# AQC + 트로터 동역학을 활용한 중성자 산란 시뮬레이션 서버리스 워크플로우\n",
        "\n",
        "예상 *소요 시간: Heron r3 프로세서 기준 18분 (참고: 이는 단지 예상치일 뿐입니다. (실행 시간은 다를 수 있습니다.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "learning-outcomes",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 학습 성과\n",
        "\n",
        "* 비탄성 중성자 산란 스펙트럼이 1D 양자 자석의 동적 구조 인자 $S(q, \\omega)$ 와 어떻게 대응하는가.\n",
        "* 밀도 행렬 재정규화 군(DMRG) 및 행렬 곱 상태(MPS) 충실도 극대화를 이용하여 KCuF$_3$ (등방성 하이젠베르크) 기저 상태를 준비하는 방법.\n",
        "* Trotter 시간 진화, 근사 양자 컴파일링(AQC) 회로 압축 및 완화 실행을 단일 함수 호출로 실행하는 방법.\n",
        "* 사이트별 $\\langle \\sigma_z \\rangle(t)$ 시계열 데이터를 $S(q, \\omega)$ 로 후처리하고, 두 스핀온 연속체를 식별하는 방법.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "prerequisites",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 전제조건\n",
        "\n",
        "* [Qiskit 패턴](/docs/guides/intro-to-patterns), [`SparsePauliOp`](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp), 및 에 대한 이해 [트로터의 시간적 변화](/learning/courses/utility-scale-quantum-computing/quantum-simulation).\n",
        "* 텐서 네트워크 기법(DMRG 및 MPS)에 대한 기초적인 이해는 도움이 되지만 필수는 아니며, 이 함수가 트로터 회로를 압축하는 데 사용하는 라이브러리에 [`qiskit-addon-aqc-tensor`](https://github.com/Qiskit/qiskit-addon-aqc-tensor) 대한 지식도 마찬가지입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 배경\n",
        "\n",
        "비탄성 중성자 산란은 스핀-스핀 상관 함수의 시공간 푸리에 변환인 동적 구조 인자 $S(q, \\omega)$ 를 측정하므로, 미시적 스핀 모델로부터 $S(q, \\omega)$ 를 재현하는 것은 양자 시뮬레이션에 대한 직접적이고 반증 가능한 검증 방법이다. 이 튜토리얼에서는 KCuF$_3$를 다룹니다. 이는 스핀- $\\frac{1}{2}$ 인 반강자성 하이젠베르크 사슬로, 여기 상태는 단일 스핀 반전이 아니라 분수화된 스피논 쌍으로 나타납니다. 즉, $S(q, \\omega)$ 는 날카로운 마그논 분산 곡선 대신, 아래쪽은 $\\tfrac{\\pi}{2}|\\sin q|$ 로, 위쪽은 $\\pi|\\sin(q/2)|$ 로 경계 지워진 넓은 *2-스피논 연속체를* 보여줍니다. 이는 다음 그림에서 점선으로 표시된 곡선들입니다. 전체적인 물리학적 원리와 측정된 중성자 데이터와의 비교에 대해서는 [원본](/docs/tutorials/simulate-neutron-scattering) 튜토리얼과 Lee 등(20XX)의 논문에서 다루고 있으며, [arXiv:2603.15608](https://arxiv.org/abs/2603.15608).\n",
        "\n",
        "양자 워크플로는 산란 실험을 반영합니다:\n",
        "\n",
        "1. 사슬의 기저 상태 $|\\psi_0\\rangle$ 를 준비합니다.\n",
        "2. 중심 지점에서 국소 섭동, 즉 $\\pi/2$ $Z$ -회전을 가하여, 중성자로부터의 운동량 및 에너지 전달을 모방해 봅시다.\n",
        "3. 트로터 곱 공식(Trotter product formula)을 사용하여 하이젠베르크 해밀토니안 $e^{-iHt}$ 하에서 시간 발전을 수행한다.\n",
        "4. 사이트별 자화 $\\langle \\sigma_z^j \\rangle(t)$ 를 측정합니다. 이는 사이트 $j$ 와 시간 $t$ 에 대한 함수로서, 정확히 지연 그린 함수 $G^R(j, j_c, t)$ 와 동일하므로, 5단계에서 푸리에 변환을 수행하기 전에 별도의 변환 과정이 필요하지 않습니다.\n",
        "5. 푸리에 변환을 통해 $G^R$ 를 $S(q, \\omega)$ 로 변환한다.\n",
        "\n",
        "3단계에서, 긴 진화 과정에 대한 정확한 트로터 회로가 하드웨어가 처리하기에는 너무 깊어질 때 문제가 발생할 수 있다. 텐서 네트워크를 활용한 AQC는 트로터 단계의 한 블록을 고정된 얕은 매개변수화 안자츠로 압축함으로써 이 문제를 해결하며, 이 안자츠의 정확한 진화에 대한 상태 충실도는 MPS 시뮬레이터를 통해 고전적으로 최대화됩니다( [arXiv:2301.08609](https://arxiv.org/abs/2301.08609) ). [AQC Dynamics 템플릿](/docs/guides/function-template-aqc-trotter) 은 이 전체 양자 코어(트로터 합성, AQC 압축, 완화 실행)를 하나의 호출로 묶어 제공합니다:\n",
        "\n",
        "| PRE (이 노트)                                              | FUNCTION (`aqc-dynamics-function`)                                                                        | 게시하기 (이 노트)              |\n",
        "| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------ |\n",
        "| DMRG와 MPS 정확도 극대화를 통해 도출한 기저 상태. 중성자 킥이 동일한 회로에 반영되어 있음 | 트로터 합성 → AQC 압축 → `statevector`, `fake`, 또는 에서 실행하여 `runtime` 각 사이트당 $\\langle \\sigma_z^j \\rangle(t)$ 를 반환 | $S(q, \\omega)$, 동적 구조 인자 |\n",
        "\n",
        "실험에 특화된 작업은 이 노트북에 남아 있습니다: 기저 상태 준비(PRE)와 ‘ $S(q, \\omega)$ ’ 후처리(POST). 양자 연산이 많이 필요한 두 단계, 즉 압축과 실행은 함수 내부에서 수행됩니다.\n",
        "\n",
        "이 튜토리얼은 [“양자 회로를 이용한 양자 물질에서의 중성자 산란 시뮬레이션”](/docs/tutorials/simulate-neutron-scattering) 문서의 보조 자료로, 해당 문서와 동일한 실험을 단계별로 구현합니다. 즉, 동일한 KCuF$_3$ 모델, 기저 상태 준비, 중성자 킥, 후처리를 다루며, 트로터 합성, AQC 압축, 완화 실행 과정을 단계별로 상세히 설명합니다. AQC 압축이 어떻게 작동하는지 알아보려면 해당 튜토리얼을 읽어보세요. 배포된 함수 템플릿을 통해 동일한 실험을 실행하려면 이 글을 읽어보세요. 양자 코어가 단일 함수 호출로 변환되며, 몇 시간씩 걸리는 AQC 압축 작업이 사용자의 컴퓨터가 아닌 서버리스 워커 내에서 실행되므로, 작업이 진행되는 동안 HPC 시스템이나 오픈 커널이 필요하지 않습니다. 이 호출은 다른 ‘ 1D ’ 역학 실험들도 구동합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 요구사항\n",
        "\n",
        "이 튜토리얼을 시작하기 전에 다음 사항이 준비되어 있는지 확인하십시오:\n",
        "\n",
        "* Qiskit Serverless 계정에 배포된 함수입니다. 먼저 컴패니언 함수 템플릿을 실행하십시오: [AQC + Trotter 동역학 함수 템플릿을 배포하고 실행하십시오](/docs/guides/function-template-aqc-trotter). 이 가이드에서는 소스 파일을 가져오고 해당 함수를 귀하의 계정에 업로드하는 방법을 단계별로 안내합니다. 이 튜토리얼에서는 배포된 함수만 호출합니다.\n",
        "\n",
        "* IBM Quantum® ( `QiskitServerless` 함수 템플릿 참조)에 저장된 자격 증명. 이 튜토리얼에 나오는 두 예제 모두 배포된 함수를 호출하므로, 두 예제 모두 해당 함수가 필요합니다.\n",
        "\n",
        "* Qiskit SDK v2.0 또는 그 이후 버전 (`pip install qiskit`).\n",
        "\n",
        "* Qiskit IBM 카탈로그 클라이언트 (`pip install qiskit-ibm-catalog`).\n",
        "\n",
        "* NumPy, SciPy, 그리고 Matplotlib (`pip install numpy scipy matplotlib`). 기저 상태 준비에 사용되는 COBYQA 최적화기를 사용하려면 SciPy 1.14 이상의 버전이 필요합니다.\n",
        "\n",
        "* AQC 텐서 네트워크 스택은, 1단계의 기저 상태 준비 과정이 이 노트북 내에서 로컬로 실행되기 때문입니다: `pip install 'qiskit-addon-aqc-tensor[quimb-jax]==0.3.1'`.\n",
        "\n",
        "새로 배포된 함수에 대한 첫 번째 호출은 서버리스 워커가 의존성을 설치하는 동안 대기하므로, 해당 실행 시 추가적인 지연 시간이 발생할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-md",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## 설정\n",
        "\n",
        "라이브러리를 불러오고, 나중에 사용될 실험별 헬퍼 함수들을 정의합니다: ( `build_gs_ansatz` 기저 상태 준비를 위한 해밀토니안 변분 접근법, 즉 HVA), `prepare_ground_state` (DMRG 및 MPS 충실도 극대화), 그리고 `get_spectrum`, `plot_green`, 및 `plot_spectrum` ( $S(q, \\omega)$ 후처리). 이 내용은 [원본 중성자 산란 튜토리얼을](/docs/tutorials/simulate-neutron-scattering) 바탕으로 수정 및 보완한 것입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "setup-imports",
      "metadata": {},
      "outputs": [],
      "source": [
        "from functools import partial\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import scipy.optimize\n",
        "\n",
        "import quimb.tensor as qtn\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit\n",
        "from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator\n",
        "from qiskit_ibm_catalog import QiskitServerless"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "setup-helpers",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Setup complete - helpers defined.\n"
          ]
        }
      ],
      "source": [
        "#  Dynamical structure factor via discrete Fourier transform\n",
        "\n",
        "\n",
        "def get_spectrum(n, Gjjc, dt, time_steps, q_steps, w_steps):\n",
        "    \"\"\"Compute the dynamical structure factor from the retarded Green's function.\n",
        "\n",
        "    Uses the center-site approximation and a discrete Fourier transform.\n",
        "    \"\"\"\n",
        "    green = Gjjc / 4  # sigma -> S=1/2\n",
        "    omega_max = np.pi / dt\n",
        "    qpoints = np.arange(0, 2 * np.pi, 2 * np.pi / q_steps)\n",
        "    omegas = np.arange(0, omega_max, omega_max / w_steps)\n",
        "    green_map = np.zeros((omegas.shape[0], qpoints.shape[0]))\n",
        "    center = n // 2 - 1\n",
        "    for iw, w in enumerate(omegas):\n",
        "        exponent = np.exp(1j * w * dt * np.arange(1, time_steps + 1))\n",
        "        S_w = np.dot(green.T, exponent) * dt\n",
        "        for iq, q in enumerate(qpoints):\n",
        "            q_matrix = np.exp(-1j * q * np.arange(-center, center + 2, 1))\n",
        "            green_map[iw, iq] = np.imag(np.dot(S_w, q_matrix))\n",
        "    return green_map\n",
        "\n",
        "\n",
        "#  Plotting helpers\n",
        "\n",
        "\n",
        "def plot_spectrum(\n",
        "    dsf,\n",
        "    dt,\n",
        "    q_steps,\n",
        "    w_steps,\n",
        "    lower_bound=False,\n",
        "    upper_bound=False,\n",
        "    title=None,\n",
        "):\n",
        "    \"\"\"Heat-map of the dynamical structure factor.\"\"\"\n",
        "    omega_max = np.pi / dt\n",
        "    qpoints = np.arange(0, 2 * np.pi, 2 * np.pi / q_steps)\n",
        "    omegas = np.arange(0, omega_max, omega_max / w_steps)\n",
        "    x, y = np.meshgrid(qpoints, omegas)\n",
        "    fig, ax = plt.subplots(figsize=(8, 5))\n",
        "    c = ax.pcolormesh(x, y, dsf / np.max(dsf), cmap=\"viridis\", shading=\"auto\")\n",
        "    fig.colorbar(c, ax=ax, label=\"Normalized intensity\")\n",
        "    if lower_bound:\n",
        "        ax.plot(\n",
        "            qpoints,\n",
        "            np.pi * np.abs(np.sin(qpoints)) / 2,\n",
        "            \"--\",\n",
        "            color=\"white\",\n",
        "            lw=1.5,\n",
        "            label=\"Lower bound\",\n",
        "        )\n",
        "    if upper_bound:\n",
        "        ax.plot(\n",
        "            qpoints,\n",
        "            np.pi * np.abs(np.sin(qpoints / 2)),\n",
        "            \"--\",\n",
        "            color=\"red\",\n",
        "            lw=1.5,\n",
        "            label=\"Upper bound\",\n",
        "        )\n",
        "    ax.set_ylim(0, 3.6)\n",
        "    ax.set_xlim(0, 2 * np.pi - 2 * np.pi / q_steps)\n",
        "    ax.set_xlabel(r\"$q$\", fontsize=16)\n",
        "    ax.set_ylabel(r\"$\\tilde{\\omega} = \\omega / J$\", fontsize=16)\n",
        "    ax.set_xticks([0, np.pi / 2, np.pi, 3 * np.pi / 2, 2 * np.pi])\n",
        "    ax.set_xticklabels([\"0\", r\"$\\pi/2$\", r\"$\\pi$\", r\"$3\\pi/2$\", r\"$2\\pi$\"])\n",
        "    if lower_bound or upper_bound:\n",
        "        ax.legend(loc=\"upper right\", fontsize=11)\n",
        "    if title:\n",
        "        ax.set_title(title, fontsize=14)\n",
        "    plt.tight_layout()\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "def plot_green(n, Gjjc, time_steps, dt, title=None):\n",
        "    \"\"\"Heat-map of the retarded Green's function in real space and time.\"\"\"\n",
        "    fig, ax = plt.subplots(figsize=(8, 6))\n",
        "    t_axis = np.arange(1, time_steps + 1) * dt\n",
        "    site_axis = np.arange(n)\n",
        "    x, y = np.meshgrid(t_axis, site_axis)\n",
        "    c = ax.pcolormesh(\n",
        "        x,\n",
        "        y,\n",
        "        np.real(Gjjc).T,\n",
        "        cmap=\"RdBu\",\n",
        "        vmax=0.5,\n",
        "        vmin=-0.5,\n",
        "        shading=\"auto\",\n",
        "    )\n",
        "    fig.colorbar(c, ax=ax, label=r\"Re $G^R(j, j_c, t)$\")\n",
        "    ax.set_xlabel(r\"Time  ($t / J^{-1}$)\", fontsize=16)\n",
        "    ax.set_ylabel(\"Site index $j$\", fontsize=16)\n",
        "    if title:\n",
        "        ax.set_title(title, fontsize=14)\n",
        "    plt.tight_layout()\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "#  Variational ground-state ansatz (HVA)\n",
        "\n",
        "\n",
        "def _apply_xxz_pair_gate(qc, q0, q1, theta):\n",
        "    \"\"\"Apply the parameterized XXZ-type two-qubit gate used in the HVA.\"\"\"\n",
        "    qc.cx(q0, q1)\n",
        "    qc.rz(theta, q1)\n",
        "    qc.h(q0)\n",
        "    qc.rz(theta + np.pi / 2, q0)\n",
        "    qc.cx(q0, q1)\n",
        "    qc.rz(-theta, q1)\n",
        "    qc.h(q1)\n",
        "    qc.cx(q1, q0)\n",
        "    qc.rz(np.pi / 2, q1)\n",
        "    qc.rz(-np.pi / 2, q0)\n",
        "    qc.h(q1)\n",
        "    qc.h(q0)\n",
        "\n",
        "\n",
        "def build_gs_ansatz(n, params, layers):\n",
        "    \"\"\"Build the Hamiltonian variational ansatz (HVA) circuit for\n",
        "    ground-state preparation of the 1D Heisenberg model.\n",
        "\n",
        "    Starts from a product of singlet pairs and applies alternating\n",
        "    odd/even layers of parameterized XXZ gates. For layer r,\n",
        "    params[2 * r] is the odd-layer (inter-pair) angle and\n",
        "    params[2 * r + 1] is the even-layer (intra-pair) angle.\n",
        "    \"\"\"\n",
        "    qc = QuantumCircuit(n)\n",
        "    # Initial singlet product state\n",
        "    for i in range(n // 2):\n",
        "        qc.x(2 * i)\n",
        "        qc.x(2 * i + 1)\n",
        "        qc.h(2 * i + 1)\n",
        "        qc.cx(2 * i + 1, 2 * i)\n",
        "    # Variational layers\n",
        "    for r in range(layers):\n",
        "        for i in range(1, (n + 1) // 2):  # odd layer\n",
        "            _apply_xxz_pair_gate(qc, 2 * i - 1, 2 * i, params[2 * r])\n",
        "        for i in range(n // 2):  # even layer\n",
        "            _apply_xxz_pair_gate(qc, 2 * i, 2 * i + 1, params[2 * r + 1])\n",
        "    return qc\n",
        "\n",
        "\n",
        "def prepare_ground_state(n, gs_layers=5, max_bond=128, cutoff=1e-8):\n",
        "    \"\"\"Prepare the KCuF3 (isotropic Heisenberg) ground state as a QuantumCircuit.\n",
        "\n",
        "    Runs DMRG (quimb MPO + DMRG2) to get the chain's ground state, then optimizes\n",
        "    the HVA angles to maximize the MPS overlap |<psi_ansatz|psi_DMRG>|^2. No exact\n",
        "    diagonalization, so it scales to larger n.\n",
        "    \"\"\"\n",
        "    J = Jz = 1.0\n",
        "    builder = qtn.SpinHam1D(S=1 / 2)\n",
        "    builder += J * 0.5, \"+\", \"-\"\n",
        "    builder += J * 0.5, \"-\", \"+\"\n",
        "    builder += Jz, \"Z\", \"Z\"\n",
        "    H_mpo = builder.build_mpo(L=n)\n",
        "    dmrg = qtn.DMRG2(H_mpo)\n",
        "    dmrg.solve(tol=1e-8, verbosity=0)\n",
        "\n",
        "    gs_sim = QuimbSimulator(\n",
        "        quimb_circuit_factory=partial(\n",
        "            qtn.CircuitMPS, gate_opts=dict(cutoff=cutoff, max_bond=max_bond)\n",
        "        ),\n",
        "        autodiff_backend=\"jax\",\n",
        "    )\n",
        "\n",
        "    def gs_infidelity(params):\n",
        "        psi = tensornetwork_from_circuit(\n",
        "            build_gs_ansatz(n, params, gs_layers), gs_sim\n",
        "        ).psi\n",
        "        return 1 - abs(psi.H @ dmrg.state) ** 2\n",
        "\n",
        "    # Seed and optimizer match the original tutorial. Each layer starts at\n",
        "    # [0, pi/2]: an odd-layer angle of 0 makes the inter-pair gate the identity,\n",
        "    # and an even-layer angle of pi/2 makes the intra-pair gate a SWAP (since\n",
        "    # 0.5 * (XX + YY + ZZ) = SWAP - I/2). That puts the seed at the singlet-pair\n",
        "    # product limit, which is already a decent approximation to the Heisenberg\n",
        "    # ground state, so the optimizer only has to refine it. The small jitter\n",
        "    # (fixed RNG seed, so runs are reproducible) breaks the exact symmetry\n",
        "    # between layers; COBYQA then runs for up to 100 iterations.\n",
        "    rng = np.random.default_rng(12345)\n",
        "    x0 = np.tile([0.0, np.pi / 2], gs_layers) + rng.normal(\n",
        "        scale=0.1, size=2 * gs_layers\n",
        "    )\n",
        "    result_gs = scipy.optimize.minimize(\n",
        "        gs_infidelity, x0, method=\"COBYQA\", options={\"maxiter\": 100}\n",
        "    )\n",
        "    print(f\"DMRG ground-state energy: {dmrg.energy:.6f}\")\n",
        "    print(f\"GS fidelity: {1 - result_gs.fun:.4f}\")\n",
        "    return build_gs_ansatz(n, result_gs.x, gs_layers)\n",
        "\n",
        "\n",
        "print(\"Setup complete - helpers defined.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-load-md",
      "metadata": {},
      "source": [
        "<span id=\"load-the-function-template\" />\n",
        "\n",
        "### 함수 템플릿 불러오기\n",
        "\n",
        "Qiskit Serverless 에 접속하여 배포된 파일을 불러오세요 `aqc-dynamics-function`. 이 튜토리얼에 나오는 두 예제 모두 동일한 핸들을 `fn` 호출하므로, 이 함수는 여기서 한 번만 로드됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "setup-connect",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Credentials are read from the account saved once via QiskitServerless.save_account(...)\n",
        "serverless = QiskitServerless()\n",
        "fn = serverless.load(\"aqc-dynamics-function\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-md",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## 소규모 시뮬레이터 예시\n",
        "\n",
        "먼저 정확한 `statevector` 백엔드를 사용하여 10개 사이트로 구성된 소규모 체인에서 전체 워크플로를 실행합니다. 이를 통해 QPU 실행 시간을 사용하기 전에 PRE → FUNCTION → POST 파이프라인을 검증합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s1-md",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### 1단계: 고전적 입력을 양자 문제에 매핑하기\n",
        "\n",
        "KCuF$_3$ 해밀토니안을 ( `SparsePauliOp` 등방성 하이젠베르크: $XX + YY + ZZ$ ) 형태로 구성하되, 각 가장 가까운 이웃 결합에 대해 결합 상수 $\\tfrac14$ 를 적용한다. 이 끈들은 파울리 연산자이므로, $\\tfrac14$ 를 통해 스핀- $\\frac{1}{2}$ 결합을 얻을 수 있다. DMRG와 MPS-정확도 극대화를 통해 기저 상태를 준비한 다음, 중성자 킥을 적용합니다: 중심 사이트에서 $\\pi/2$ $Z$ 회전을 가합니다. 이렇게 준비된 회로가 바로 함수에 로 전달되는 것입니다 `initial_state`. 우리는 이를 기본값(사이트별 $Z$ )으로 `observables` 그대로 두는데, 이는 중성자 워크플로우에 필요한 $\\langle \\sigma_z^j \\rangle(t)$ 출력값과 정확히 일치합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "small-s1-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DMRG ground-state energy: -4.258035\n",
            "GS fidelity: 0.9841\n",
            "Prepared 10-qubit ground state with the neutron kick at site 4.\n"
          ]
        }
      ],
      "source": [
        "n = 10\n",
        "dt = 0.6  # physical time per Trotter step (also the omega-axis unit in POST)\n",
        "time_steps = 10\n",
        "center = n // 2 - 1\n",
        "\n",
        "# MPS-simulator settings, shared by the ground-state prep here and the AQC\n",
        "# compression inside the function (matches the original tutorial).\n",
        "mps_max_bond = 32\n",
        "mps_cutoff = 1e-8\n",
        "\n",
        "# 1D isotropic Heisenberg (KCuF3) Hamiltonian on n qubits\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(p, [i, i + 1], 0.25) for i in range(n - 1) for p in (\"XX\", \"YY\", \"ZZ\")],\n",
        "    num_qubits=n,\n",
        ")\n",
        "\n",
        "# Ground state (DMRG + fidelity max) + neutron kick baked into the same circuit\n",
        "gs_circuit = prepare_ground_state(\n",
        "    n, gs_layers=3, max_bond=mps_max_bond, cutoff=mps_cutoff\n",
        ")\n",
        "gs_circuit.rz(\n",
        "    np.pi / 2, center\n",
        ")  # exp(-i (pi/2)/2 Z_center): the neutron perturbation\n",
        "print(\n",
        "    f\"Prepared {n}-qubit ground state with the neutron kick at site {center}.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s3-md",
      "metadata": {},
      "source": [
        "<span id=\"steps-2-and-3-compress-and-execute-with-the-function-template\" />\n",
        "\n",
        "### 2단계 및 3단계: 함수 템플릿을 사용하여 압축하고 실행하기\n",
        "\n",
        "수작업으로 진행되는 워크플로우에서는 이 두 가지가 별도의 단계로 나뉩니다. 즉, 하드웨어에 맞게 회로를 최적화하는 단계(2단계)와 이를 실행하는 단계(3단계)입니다. 이 함수 템플릿은 두 가지를 하나의 호출로 통합합니다. 이 과정에서는 트로터 합성, AQC 압축 및 하드웨어 트랜스파일링을 수행한 뒤, 회로를 실행합니다(이 단계에서는 정확한 시뮬레이터에서 실행되며, 이후 하드웨어에서는 내장된 오류 완화 기능을 적용하여 실행됩니다). 두 가지 튜닝 매개변수는 `aqc_segments` (압축 계획)과 `aqc_options` (MPS 및 최적화기 설정)입니다. 각 세그먼트는 연속된 `k` 트로터 단계를 `m`-단계 트로터 목표값으로 구성된 안자츠로 `{\"n_steps\": k, \"ansatz_steps\": m}` 압축하며, 이를 초과하는 단계는 일반 트로터 방식으로 `sum(n_steps)` 실행됩니다. 초기 단계의 낮은 얽힘을 가진 단계들은 얕은 (`ansatz_steps=1`) 안자츠로 잘 압축되므로, 여기서는 처음 세 단계를 단일층 안자츠로, 다음 두 단계를 더 깊은 2층 안자츠로 압축합니다. 10개의 트로터 단계 중 나머지 5개는 일반 트로터 방식으로 실행됩니다. 이는 원본 튜토리얼을 그대로 따르는 것으로, MPS 결합 치수 `max_bond=32`, `cutoff=1e-8`, 그리고 반복 횟수를 100회로 제한한 L-BFGS-B 최적화기를 사용합니다 `aqc_options` .\n",
        "\n",
        "Setup에서 로드된 함수를 호출하십시오. `backend=\"statevector\"` 정확한 참조 경로를 실행합니다: QPU 시간이 소요되지 않으며, 회로는 서버리스 워커 내부의 정확한 상태 벡터 시뮬레이터에서 실행됩니다(이를 호출하려면 여전히 저장된 Qiskit Serverless 계정이 필요합니다). 는 준비된 기저 상태(킥 포함)를 `initial_state` 나타내며, `observables` 는 생략되어 이 함수가 기본 사이트당 $Z$ 를 측정하도록 합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "small-s3-run",
      "metadata": {},
      "outputs": [],
      "source": [
        "job = fn.run(\n",
        "    t_steps=time_steps,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 3,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 2,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    aqc_options={\n",
        "        \"max_bond\": mps_max_bond,  # MPS bond dimension for AQC compression\n",
        "        \"cutoff\": mps_cutoff,\n",
        "        \"optimizer_settings\": {\n",
        "            \"method\": \"L-BFGS-B\",\n",
        "            \"jac\": True,\n",
        "            \"options\": {\"maxiter\": 100},\n",
        "        },\n",
        "    },\n",
        "    dt=dt,\n",
        "    hamiltonian=H,\n",
        "    initial_state=gs_circuit,  # prepared ground state including the neutron kick\n",
        "    # observables omitted -> default per-site Z (the neutron sigma_z readout)\n",
        "    backend=\"statevector\",\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "small-status",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(job.status())  # rerun this cell until status says DONE"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "small-s3-result",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "AQC fidelities: {'1': 1.0, '2': 0.9999, '3': 0.9992, '4': 0.9998, '5': 0.9995}\n",
            "Green's function shape: (10, 10)\n"
          ]
        }
      ],
      "source": [
        "# The per-site <sigma_z>(t) the function returns is the retarded Green's function\n",
        "# G(j, j_c, t). The workflow samples t = 1..time_steps, so drop the t = 0 row (the\n",
        "# prepared+kicked state before any evolution) before post-processing.\n",
        "result = job.result()\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "Gjjc = ev[1:]  # shape (time_steps, n)\n",
        "print(\"Green's function shape:\", Gjjc.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s4-md",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### 4단계: 후처리를 수행하고 원하는 기존 형식으로 결과를 반환합니다\n",
        "\n",
        "그린 함수에 푸리에 변환을 적용해 $S(q, \\omega)$ 를 구하고, 대칭 변환을 수행한 뒤 음수 값을 잘라내는 것이 표준 중성자 후처리 과정입니다. 이 모델의 경우 $S(q, \\omega) = S(-q, \\omega)$ 이므로 미러링은 정확히 이루어지며, 남아 있는 음수 값들은 유한하고 이산적으로 샘플링된 시계열을 푸리에 변환할 때 발생하는 인공적인 현상이므로 0으로 잘립니다. 이 작은 정확한 시뮬레이션에서는 2스핀온 연속체가 대략적으로만 분해되지만, 그 작동 원리는 뒤이어 수행되는 하드웨어 시뮬레이션과 동일합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "small-s4-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/small-s4-code-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/small-s4-code-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "q_res, w_res = 100, 100\n",
        "spectrum = get_spectrum(n, Gjjc, dt, time_steps, q_res, w_res)\n",
        "spectrum = -(spectrum + spectrum[:, ::-1]) / 2  # mirror symmetry\n",
        "spectrum = np.clip(spectrum, a_min=0, a_max=None)  # clip negatives\n",
        "\n",
        "plot_green(\n",
        "    n,\n",
        "    Gjjc,\n",
        "    time_steps,\n",
        "    dt,\n",
        "    title=f\"Retarded Green's function - {n} qubits (AQC, statevector)\",\n",
        ")\n",
        "plot_spectrum(\n",
        "    spectrum,\n",
        "    dt,\n",
        "    q_res,\n",
        "    w_res,\n",
        "    lower_bound=True,\n",
        "    upper_bound=True,\n",
        "    title=f\"Dynamical structure factor - {n} qubits (AQC, statevector)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-md",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## 대규모 하드웨어 예시\n",
        "\n",
        "과학 코드의 변경 없이 동일한 워크플로우를 확장할 수 있습니다. 30개 사이트 체인, 두 배의 트로터 깊이(20단계), 다양한 가설 깊이를 사용하는 압축 계획(후반부의 더 얽힌 단계에는 더 깊은 가설 사용), 그리고 함수에 내장된 오류 완화 기능(동적 분리, 파울리 트월링, 트월링된 판독 오류 소멸(TREX))을 갖춘 IBM Quantum 프로세서에서의 실행이 포함됩니다. 시뮬레이터 예제와 동일한 4단계 과정을 따르며, Setup에서 생성된 `fn` 핸들을 재사용합니다.\n",
        "\n",
        "|                     | 작은 축척         | 큰 축척                      |\n",
        "| ------------------- | ------------- | ------------------------- |\n",
        "| 큐비트                 | 1,000만        | 6시간에서 30분                 |\n",
        "| 트로터 스텝              | 1,000만        | 20                        |\n",
        "| AQC 압축 단계 (1층 + 2층) | 3 + 2 = 5     | 6 + 4 = 10                |\n",
        "| 기저 상태 가설 층          | 3             | 5                         |\n",
        "| MPS 최대 본드 치수        | 32            | 128                       |\n",
        "| 백엔드                 | `statevector` | DD, 파울리 회전, TREX가 적용된 QPU |\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s1-md",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### 1단계: 고전적 입력을 양자 문제에 매핑하기\n",
        "\n",
        "KCuF$_3$와 동일한 하이젠베르크 구조를 구축하고 `SparsePauliOp` , 더 긴 사슬에 대해 더 깊은 안자츠 `gs_layers=5` (ansatz)를 적용하여 기저 상태를 준비한 다음, 중앙 사이트에 $\\pi/2$ $Z$ 중성자 킥을 주입합니다. 이는 소규모 매핑과 동일하지만, $n = 30$ 에서 확인할 수 있습니다.\n",
        "\n",
        "10-site 시뮬레이션보다 낮은 기저 상태 충실도를 예상할 수 있습니다. 5개의 HVA 층으로는 30-site 기저 상태를 완전히 포착할 수 없기 때문에, 여기서는 약 0.82 의 값이 나오는 반면, 더 작은 사슬의 경우 0.98 의 값을 보였습니다. 이는 실패라기보다는 예상되는 현상이며, 같은 이유로 원본 튜토리얼에서는 50개 사이트에서 대략 0.65 을 허용하고 있습니다. COBYQA 반복 횟수 상한을 높이면 성능이 향상되지만 `gs_layers` , 추가적인 고전적 비용이 발생합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "large-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DMRG ground-state energy: -13.111355\n",
            "GS fidelity: 0.8201\n",
            "Prepared 30-qubit ground state with the neutron kick at site 14.\n"
          ]
        }
      ],
      "source": [
        "n = 30\n",
        "dt = 0.6\n",
        "time_steps = 20\n",
        "center = n // 2 - 1\n",
        "\n",
        "# Same MPS settings as the original large-scale run: a larger bond for the\n",
        "# longer, more-entangled chain (shared by GS prep and AQC compression).\n",
        "mps_max_bond = 128\n",
        "mps_cutoff = 1e-8\n",
        "\n",
        "# Same KCuF3 Hamiltonian and ground-state prep, on a larger chain\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(p, [i, i + 1], 0.25) for i in range(n - 1) for p in (\"XX\", \"YY\", \"ZZ\")],\n",
        "    num_qubits=n,\n",
        ")\n",
        "gs_circuit = prepare_ground_state(\n",
        "    n, gs_layers=5, max_bond=mps_max_bond, cutoff=mps_cutoff\n",
        ")\n",
        "gs_circuit.rz(np.pi / 2, center)  # neutron kick at the center site\n",
        "print(\n",
        "    f\"Prepared {n}-qubit ground state with the neutron kick at site {center}.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s3-md",
      "metadata": {},
      "source": [
        "<span id=\"steps-2-and-3-compress-and-execute-with-the-function-template\" />\n",
        "\n",
        "### 2단계 및 3단계: 함수 템플릿을 사용하여 압축하고 실행하기\n",
        "\n",
        "시뮬레이터 예제와 동일한 단일 호출이지만, 이번에는 IBM Quantum 프로세서를 `backend_name` 가리키도록 설정되어 있어, 해당 함수가 그곳에서 트랜스파일링되어 실행됩니다. 압축 계획에 따라 안자츠의 깊이가 달라집니다. 처음 6단계(저얽힘)의 트로터 단계는 얕은 단일층 안자츠로 압축되고, 다음 4단계는 더 깊은 2층 안자츠로 압축되며, 20단계 중 나머지 10단계는 일반 트로터 방식으로 실행됩니다. `aqc_options` 더 길고 얽힘이 더 심한 사슬에 대해 `max_bond=128` MPS 결합 차원을 로 높이며(원본과 일치), L-BFGS-B 최적화기의 반복 횟수 상한을 100회로 유지합니다. 내장된 오류 완화 기능, 즉 동적 분리( XY4 ), 게이트 트위링 및 TREX 측정 완화 기능을 활성화합니다 `estimator_options` . 이 함수의 기본값은 TREX 학습 예산(`measure_noise_learning`)을 제외하고는 이미 원래 튜토리얼과 일치합니다. 호출자가 제공한 값이 함수의 기본값과 병합되는 것이 아니라 이를 전면적으로 대체하기 `estimator_options` 때문에, 전체 블록이 여전히 작성되어 있습니다. 따라서 키를 생략하면 함수의 기본값이 아닌 IBM Quantum Compute 의 기본값으로 대체됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-s3-run",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "job ID (save this to reconnect later): 43ed8d07-6d7d-4f33-b70a-7f31b765b310\n"
          ]
        }
      ],
      "source": [
        "# Steps 2 + 3: the function compresses (varied ansatz) and executes on hardware.\n",
        "job = fn.run(\n",
        "    t_steps=time_steps,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 6,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 4,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    aqc_options={\n",
        "        \"max_bond\": mps_max_bond,  # 128 for the longer chain\n",
        "        \"cutoff\": mps_cutoff,\n",
        "        \"optimizer_settings\": {\n",
        "            \"method\": \"L-BFGS-B\",\n",
        "            \"jac\": True,\n",
        "            \"options\": {\"maxiter\": 100},\n",
        "        },\n",
        "    },\n",
        "    dt=dt,\n",
        "    hamiltonian=H,\n",
        "    initial_state=gs_circuit,\n",
        "    backend_name=\"ibm_pittsburgh\",\n",
        "    # Mitigation settings from the original tutorial. Only the two\n",
        "    # measure_noise_learning values differ from the function's defaults; the rest\n",
        "    # restates them, because a caller-supplied estimator_options dict replaces the\n",
        "    # function's defaults wholesale rather than merging into them.\n",
        "    estimator_options={\n",
        "        \"environment\": {\"job_tags\": [\"TUT-SNS\"]},\n",
        "        \"dynamical_decoupling\": {\"enable\": True, \"sequence_type\": \"XY4\"},\n",
        "        \"twirling\": {\n",
        "            \"enable_gates\": True,\n",
        "            \"num_randomizations\": 1000,\n",
        "            \"shots_per_randomization\": 128,\n",
        "        },\n",
        "        \"resilience\": {\n",
        "            \"measure_mitigation\": True,\n",
        "            \"measure_noise_learning\": {\n",
        "                \"num_randomizations\": 32,\n",
        "                \"shots_per_randomization\": 100,\n",
        "            },\n",
        "        },\n",
        "    },\n",
        ")\n",
        "print(\"job ID (save this to reconnect later):\", job.job_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-reconnect-md",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"오랫동안 실행 중인 작업에 다시 연결하기\">\n",
        "  대규모 실행은 속도가 빠르지 않으며, 대부분의 시간은 QPU가 아닌 클래식 환경에서 소요됩니다. AQC 압축은 어떤 데이터도 QPU에 도달하기 전에 함수 내부에서 수행됩니다. 30개 사이트에서 이 `max_bond=128` 작업을 수행했을 때, 저희 테스트에서는 거의 4시간이 소요되었는데, 이는 이 튜토리얼 상단의 *‘사용량* 추정’에 명시된 QPU 처리 시간인 약 18분과는 대조적입니다. 대기 시간은 이 두 가지에 더해집니다. 이 노트북이나 커널이 실행되는 동안 이를 계속 열어둘 필요는 없습니다.\n",
        "\n",
        "  앞의 셀에 표시된 작업 ID를 복사하여 저장하십시오. 다음 세 개의 셀을 통해 나중에 작업을 다시 이어갈 수 있습니다:\n",
        "\n",
        "  1. 다시 연결: 새로운 커널 세션에서만 필요합니다. [‘Setup](#setup) cells’를 다시 실행하여 재구성한 다음 `serverless`, 저장해 둔 ID를 사용하여 `job` 핸들을 다시 생성하십시오. 제출한 세션에 아직 머물러 있다면 이 셀을 건너뛰세요. 핸들이 이미 활성화된 상태이기 때문입니다.\n",
        "  2. 상태 확인: 결과가 표시될 때까지 다시 실행하십시오 `DONE`.\n",
        "  3. 결과 가져오기: 상태가 일 때만 실행합니다 `DONE`.\n",
        "\n",
        "  다음 재연결 셀에는 자리 표시자가 들어 있습니다. 다음 내용을 본인의 내용으로 바꾸세요 `job_id`:\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-reconnect",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Reconnect to a previously submitted job by its ID. Only needed in a NEW kernel\n",
        "# session; if you are still in the session where you submitted, the `job` handle\n",
        "# from the preceding cell is already live, so skip this cell. Replace the ID that follows with your own.\n",
        "job = serverless.get_job_by_id(\"<your job ID>\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "large-status",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "# Check where the job is. Re-run this until it reports DONE before fetching the\n",
        "# result in the following cell: QUEUED -> INITIALIZING -> RUNNING: OPTIMIZING_FOR_HARDWARE ->\n",
        "# RUNNING: WAITING_FOR_QPU -> RUNNING: EXECUTING_QPU -> RUNNING: POST_PROCESSING\n",
        "# -> DONE.\n",
        "print(job.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-s3-result",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "AQC fidelities: {'1': 1.0, '2': 0.9994, '3': 0.9944, '4': 0.9853, '5': 0.9747, '6': 0.959, '7': 0.9495, '8': 0.9542, '9': 0.9533, '10': 0.9451}\n"
          ]
        }
      ],
      "source": [
        "# Run this only once the preceding status cell reports DONE. result() blocks until\n",
        "# the job finishes, so calling it earlier just waits (possibly for hours).\n",
        "result = job.result()\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "Gjjc = ev[1:]  # drop the t = 0 row -> shape (time_steps, n)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s4-md",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### 4단계: 후처리를 수행하고 원하는 기존 형식으로 결과를 반환합니다\n",
        "\n",
        "시뮬레이터 실행과 동일한 후처리 과정을 거칩니다: 그린 함수에 푸리에 변환을 적용하여 $S(q, \\omega)$ 를 구하고, 대칭 변환을 수행한 뒤 음수 값을 잘라냅니다. 더 긴 사슬과 진화를 통해, 두 스핀온 연속체가 훨씬 더 명확하게 구분됩니다. 점선으로 표시된 경계 사이의 영역을 채워야 하며, $q = \\pi$ 근처에서 가장 밝아야 합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "large-result",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/large-result-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/large-result-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "n = result[\"metadata\"][\"n\"]\n",
        "q_res, w_res = 100, 100\n",
        "spectrum = get_spectrum(n, Gjjc, dt, time_steps, q_res, w_res)\n",
        "spectrum = -(spectrum + spectrum[:, ::-1]) / 2  # mirror symmetry\n",
        "spectrum = np.clip(spectrum, a_min=0, a_max=None)  # clip negatives\n",
        "\n",
        "plot_green(\n",
        "    n,\n",
        "    Gjjc,\n",
        "    time_steps,\n",
        "    dt,\n",
        "    title=f\"Retarded Green's function - {n} qubits (AQC, hardware)\",\n",
        ")\n",
        "plot_spectrum(\n",
        "    spectrum,\n",
        "    dt,\n",
        "    q_res,\n",
        "    w_res,\n",
        "    lower_bound=True,\n",
        "    upper_bound=True,\n",
        "    title=f\"Dynamical structure factor - {n} qubits (AQC, hardware)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "appendix-md",
      "metadata": {},
      "source": [
        "<span id=\"appendix\" />\n",
        "\n",
        "## 부록\n",
        "\n",
        "앞서 소개한 하드웨어 예제는 단일 체인 길이를 실행합니다. 다음에 제시된 세 가지 스펙트럼은 동일한 워크플로우를 10, 20, 30개 `ibm_pittsburgh` 사이트에서 수행한 초기 하드웨어 실행 결과로, 그 외 모든 입력 조건은 고정된 상태입니다. 즉, 에서 20개의 트로터 단계 `dt = 0.6`, 6개의 1층 단계와 4개의 2층 AQC 압축 단계를 포함한 압축 계획, 그리고 입니다 `max_bond = 128`. 이것들은 기록된 결과이며, 앞선 셀에서 산출된 결과가 아닙니다.\n",
        "\n",
        "세 가지 크기 모두에서 동일한 설정이 사용되었으므로, 스펙트럼을 직접 비교할 수 있습니다. 예를 들어, 체인 길이에 따라 조정하거나, 기저 상태 가설 층을 더 추가하거나 `max_bond`, 를 더 크게 설정하는 등의 방법을 사용하면, 여기에서 제시된 어떤 결과보다 더 나은 결과를 얻을 수 있습니다.\n",
        "\n",
        "![10개 위치에서의 동적 구조 인자, 하한선](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-10.avif \" 근처 q = π에서 나타나는 하나의 선명하고 밝은 피크, 10 큐비트\")\n",
        "\n",
        "![20개 사이트에서의 동적 구조 인자, 두 점선 스핀온 ](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-20.avif \"경계 사이의 대역을 채우는 스펙트럼 가중치, 20 큐비트\")\n",
        "\n",
        "![30개 위치에서의 동적 구조 인자. 연속 영역은 대비가 약해지고 경계 밖에도 일부 가중치가 부여되면서 더 세밀하게 분해되었으며, 30](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-30.avif \" 큐비트\")\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "nextsteps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 다음 단계\n",
        "\n",
        "<Admonition type=\"tip\" title=\"권장사항\">\n",
        "  * 이 워크플로를 자신의 시스템에 맞게 조정하십시오. 이 함수는 어떤 1D 의 최인접 이웃(nearest-neighbor) 방식이든 수용하므로 `SparsePauliOp`, 다른 체인 해밀토니안, 초기 상태 또는 관측량 집합을 사용하더라도 PRE → FUNCTION → POST 파이프라인은 동일하게 실행됩니다. [GitHub 의 AQC Dynamics](https://github.com/qiskit-community/qiskit-function-templates/tree/main/physics/aqc_trotter) 템플릿에서 전체 입출력 계약서를 확인하세요.\n",
        "  * 이 벤치마크가 수록된 논문을 읽어보세요: Lee 외, [*중성자 산란 실험을 통한 양자 시뮬레이션 벤치마킹*](https://arxiv.org/abs/2603.15608) ( arXiv:2603.15608 ).\n",
        "  * [원본 “중성자 산란 시뮬레이션” 튜토리얼](/docs/tutorials/simulate-neutron-scattering) 과 비교해 보면, 이 튜토리얼의 인라인 워크플로는 배포된 함수 템플릿으로 이식된 것입니다.\n",
        "  * 하드웨어 실행에 적용된 [오류 완화 및 억제 기법](/docs/guides/error-mitigation-and-suppression-techniques), 즉 동적 분리(dynamical decoupling), 파울리 트위링(Pauli twirling), TREX에 대해 더 자세히 알아보세요.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "hours": 4,
    "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"
    },
    "qpuSeconds": 1080
  },
  "nbformat": 4,
  "nbformat_minor": 5
}