{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "title-cell",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"잡음이 있는 양자 프로세서에서 관찰된 견고하고 일관된 비아벨 하드론 역학\"\n",
        "description: \"IBM 양자 하드웨어에서 LSH 프레임워크를 사용하여 SU(2) 격자 게이지 이론의 하드론 역학을 시뮬레이션한다.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore Kogut Susskind expvals Pstep Nstep vmax vmin imshow fontsize cbar Ilčić */}\n",
        "\n",
        "<span id=\"observation-of-robust-and-coherent-non-abelian-hadron-dynamics-on-noisy-quantum-processors\" />\n",
        "\n",
        "# 잡음이 있는 양자 프로세서에서 관찰된 견고하고 일관된 비아벨 하드론 역학\n",
        "\n",
        "*예상 소요 시간: Heron 프로세서(ibm\\_boston 또는 이에 상응하는 프로세서)에서 6분 (참고: 이는 단지 추정치일 뿐입니다.) (실행 시간은 다를 수 있습니다.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "learning-outcomes",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 학습 성과\n",
        "\n",
        "* 비아벨 격자 게이지 이론(특히 SU(2))을 루프-스트링-하드론(LSH) 프레임워크를 활용하여 효율적인 양자 시뮬레이션을 위해 어떻게 재구성할 수 있는가\n",
        "* 근사적 SU(2) 게이지 이론 해밀토니안을 위한 트로터화 시간 진화 회로를 구성하고 이를 큐비트에 매핑하는 방법\n",
        "* IBM Quantum® 하드웨어에서 Qiskit Estimator 프리미티브를 사용하여 판독 오차 완화 기능을 적용한 이 회로를 실행하는 방법\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 전제조건\n",
        "\n",
        "* [양자 회로 및 게이트의 기초](/learning/courses/basics-of-quantum-information)\n",
        "* [Qiskit Estimator 기본 요소 소개](/docs/guides/get-started-with-estimator)\n",
        "* 양자장론 개념에 대한 기본적인 이해 (도움이 되지만 필수는 아닙니다. ‘배경’ 섹션에서 핵심 내용을 다룹니다)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 배경\n",
        "\n",
        "<span id=\"motivation\" />\n",
        "\n",
        "### 둥기 부여\n",
        "\n",
        "강력(strong force)에 대한 SU(3) 게이지 이론인 양자 색역학(QCD)은 쿼크를 하드론으로 결합시키고, 갇힘 현상과 끈 파열을 지배한다. 고전 격자 QCD 기법은 정적 특성을 분석하는 데 탁월하지만, 부호 문제로 인해 실시간 동역학을 시뮬레이션할 수는 없다. 양자 컴퓨터는 게이지장의 자유도를 큐비트에 직접 인코딩함으로써 이러한 장벽을 우회할 수 있는 방법을 제시한다.\n",
        "\n",
        "이 튜토리얼에서는 이러한 시뮬레이션을 시연합니다. 즉, IBM Quantum 하드웨어를 사용하여 (1+1)차원 SU(2) 격자 게이지 이론에서 하드론의 실시간 전파를 시뮬레이션합니다. 이 이론은 가장 단순한 비아벨 게이지 이론이자 완전한 QCD로 나아가는 디딤돌입니다.\n",
        "\n",
        "<span id=\"the-kogut-susskind-hamiltonian\" />\n",
        "\n",
        "### 코구트-수스킨드 해밀토니안\n",
        "\n",
        "이 이론은 1D 공간 격자 위에서, 격자점에는 엇갈리게 배열된 페르미온(물질)이, 연결선에는 SU(2) 게이지장이 배치된 형태로 정립되었다. 무차원 형태로 변환하면 해밀토니안은 다음과 같다:\n",
        "\n",
        "$W = H_E^{\\text{(KS)}} + \\mu H_M + x H_I^{\\text{(KS)}},$\n",
        "\n",
        "여기서 $H_E$ 는 색전기장 에너지, $H_M$ 는 스태거드 질량 항, $H_I$ 는 물질-게이지 상호작용(호핑) 항, $\\mu = 2\\frac{m}{g}\\sqrt{x}$ 는 페르미온 질량을 나타내며, $x = \\frac{1}{g^2 a^2}$ 는 상호작용 강도이다. 이 이론의 연속체 극한은 $N \\to \\infty$ 및 $x \\to \\infty$ 에 위치한다.\n",
        "\n",
        "<span id=\"the-loop-string-hadron-lsh-framework\" />\n",
        "\n",
        "### 루프-스트링-하드론(LSH) 프레임워크\n",
        "\n",
        "주요 과제 중 하나는 각 링크에 대한 게이지장의 힐베르트 공간이 무한차원이라는 점이다. **루프-스트링-하드론(LSH)** 프레임워크는 플럭스의 루프, 분리된 전하를 연결하는 스트링, 그리고 하드론(사이트에 위치한 게이지 싱글렛 페르미온 쌍)과 같은 게이지 불변 변수들을 통해 이론을 재구성함으로써 이 문제를 해결합니다. LSH 기저에서는 가우스 법칙이 기저의 정의상 자동으로 성립하므로, 모든 기저 상태가 물리적으로 타당합니다. 각 격자 점은 루프 수 $(n_l, n_i, n_o)$, 유입 스트링, 유출 스트링을 나타내는 세 개의 양자수로 특징지어지며, 여기서 $n_i, n_o \\in \\{0,1\\}$ 는 페르미온적이며 $n_l \\geq 0$ 는 보손적이다. 이러한 값을 바탕으로, 짝수 사이트의 경우 $n_f(r) = n_i(r) + n_o(r)$, 홀수 사이트의 경우 $n_f(r) = 2 - [n_i(r) + n_o(r)]$ 로 국소 페르미온 수가 정의된다.\n",
        "\n",
        "<span id=\"from-full-hamiltonian-to-the-quantum-circuit-three-key-approximations\" />\n",
        "\n",
        "### 전체 해밀토니안에서 양자 회로까지: 세 가지 핵심 근사법\n",
        "\n",
        "이 양자 회로는 전체 SU(2) 해밀토니안을 정확히 시뮬레이션 **하지는** 않는다. 대신, 이 모델은 **약한 결합** 영역( $x \\gg 1$ )에서 유효한 일련의 통제된 근사법을 적용합니다. 무엇이 근사되고 무엇이 근사되지 않는지를 이해하는 것이 필수적입니다:\n",
        "\n",
        "**근사 1 — $H_I$ 의 약결합 극한:** 전체 상호작용 해밀토니안 $H_I^{\\text{(LSH)}}$ (식 [\\[1\\]](#references) 의 식 (16)에는 $1/\\sqrt{n_l+1}$ 와 같은 항을 통해 보손 양자수 $n_l$ 에 의존하는 선계수가 포함되어 있다. 약한 결합 영역( $x \\gg 1$ )에서는 $H_E$ 라는 전기 항이 역학을 지배하며, 이는 $n_l$ 가 큰 상태를 선호한다. $n_l \\gg 1$ 일 때, 비율 $n_l/(n_l+1) \\to 1$ 와 이 모든 선계수는 1로 단순화된다. 그러면 상호작용 해밀토니안은 순전히 국소적인 가장 가까운 이웃 간 이동으로 환원된다:\n",
        "\n",
        "$H_I^{\\text{approx}} = -\\sum_r \\left[\\sigma^-(r)\\sigma^+(r+1) + \\sigma^+(r)\\sigma^-(r+1)\\right],$\n",
        "\n",
        "이는 $n_l$ 와 무관하며, 페르미온성 $(n_i, n_o)$ 큐비트에만 작용합니다.\n",
        "\n",
        "**근사법 2 — $H_E$ 의 전역 평균 플럭스:** 전기 에너지는 각 링크의 $n_l$ 에 따라 달라진다. 약결합 진공 상태에서는 $n_l$ 가 크며 대략 균일합니다. 사이트별 $n_l$ 값을 단일 전역 평균값 $\\bar{n}_l$ 로 대체하여, $H_E$ 가 각 사이트의 페르미온 배열에 비례하는 대각 위상이 되도록 한다:\n",
        "\n",
        "$H_E^{\\text{approx}} = N h_E^0 + \\sum_{\\{r'\\}} \\left(\\frac{\\bar{n}_l}{2} + \\frac{3}{4}\\right)$\n",
        "\n",
        "여기서 $\\{r'\\}$ 는 페르미온 구성 $(n_i=0, n_o=1)$ 에 해당하는 사이트들에 대해 합을 취한 것이며, $h_E^0$ 는 무시해도 되는 전역 위상이다.\n",
        "\n",
        "**근사법 3 — 트로터화:** 지속 시간이 $\\delta_\\tau$ 인 한 단계에 대한 시간 진화 연산자는 다음과 같이 분해된다:\n",
        "\n",
        "$e^{-i\\delta_\\tau W} \\approx e^{-i\\tilde{m} H_M} \\, e^{-i\\delta_\\tau H_E^{\\text{approx}}} \\, e^{-ic H_I^{\\text{approx}}}$\n",
        "\n",
        "여기서 $c = \\delta_\\tau x$, $\\tilde{m} = \\delta_\\tau \\mu$, $\\theta = -\\delta_\\tau(\\bar{n}_l/2 + 3/4)$ 이다. 이 1차 트로터 분해는 $\\delta_\\tau \\to 0$ 일 때 사라지는 오차를 유발한다. 우리는 전체적으로 $\\delta_\\tau = 0.0015$ 를 고정한다.\n",
        "\n",
        "이 세 가지 근사법의 **결과로**, 사이트당 두 개의 페르미온 큐비트 $(n_i, n_o)$ 만이 동역학적 성질을 가지게 되며, 보손 $n_l$ 의 자유도는 유효 매개변수로 흡수되었다. 이를 통해 $N$ 개의 격자 사이트에 대해 $2N$ 개의 큐비트를 갖는 간결한 회로가 도출되며, 여기서 각 트로터 단계는 일정한 2-큐비트 게이트 깊이(단계당 13개)를 갖는다.\n",
        "\n",
        "<span id=\"what-this-tutorial-simulates\" />\n",
        "\n",
        "### 이 튜토리얼에서 시뮬레이션하는 내용\n",
        "\n",
        "이 튜토리얼은 **하드론의 전파 과정을** 시뮬레이션합니다. 강한 결합 진공 상태(곱 상태)에서 시작하여, 격자 중심에 메손을 배치한 뒤 시간이 지남에 따라 변화를 관찰합니다. 차분 측정 프로토콜 — 중심 메손이 있는 경우와 없는 경우 모두에서 회로를 구동한 뒤 그 값을 서로 뺀다 — 는 하드웨어 노이즈와 경계 효과 모두로부터 코히어런트 하드론 신호를 분리해 낸다. 그 결과,閉じ込められた 메손의 호흡 모드에 특징적인 페르미온 밀도 진동의 광원뿔 패턴이 나타난다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 요구사항\n",
        "\n",
        "이 튜토리얼을 시작하기 전에 다음을 설치하십시오:\n",
        "\n",
        "* Qiskit SDK v2.0 또는 그 이후 버전이며, [시각화 기능을](/docs/api/qiskit/visualization) 지원합니다\n",
        "* Qiskit Runtime v0.22 또는 그 이후 (`pip install qiskit-ibm-runtime`)\n",
        "* Pauli 전파 패키지 (`pip install pauli-prop`)\n",
        "* NumPy (`pip install numpy`)\n",
        "* Matplotlib (`pip install matplotlib`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-header",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## 설정\n",
        "\n",
        "먼저 필요한 라이브러리를 불러오고, LSH 시간 진화를 위한 양자 회로를 구성하는 헬퍼 함수를 정의하는 것으로 시작합니다. 회로 구성에는 세 가지 핵심 기능이 있습니다:\n",
        "\n",
        "1. **`pair_hamiltonian_circuit`**: 인접한 사이트 간의 근사 상호작용 해밀토니안에 대해 2-큐비트 유니터리 연산 $U_I$ 을 구현합니다. 게이트 분해식은 다음과 같습니다. $\\text{CNOT} \\to H \\to R_z(-c) \\to \\text{CNOT} \\to R_z(c) \\to \\text{CNOT} \\to H \\to \\text{CNOT}$.\n",
        "\n",
        "2. **`electric_hamiltonian_circuit`**: 각 사이트의 근사 전기장 에너지를 계산하기 위해 2-큐비트 유니터리 연산 $U_E$ 을 구현합니다. 게이트 분해식은 다음과 같습니다. $X \\to R_z(\\theta/2) \\to \\text{CNOT} \\to R_z(-\\theta/2) \\to \\text{CNOT} \\to R_z(\\theta/2) \\to X$.\n",
        "\n",
        "3. **`construct_circuit`**: 큐비트 연결을 관리하기 위해 SWAP 게이트를 사용하여 상호작용 항, 전기 항, 질량 항을 층층이 쌓아 완전한 트로터화 회로를 구성합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "setup-imports",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Import libraries\n",
        "\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "from matplotlib.colors import TwoSlopeNorm\n",
        "from qiskit.circuit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from typing import Optional\n",
        "\n",
        "import warnings\n",
        "\n",
        "warnings.filterwarnings(\"ignore\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "setup-functions",
      "metadata": {},
      "outputs": [],
      "source": [
        "def pair_hamiltonian_circuit(c: float) -> QuantumCircuit:\n",
        "    \"\"\"Two-qubit unitary for the approximate interaction Hamiltonian H_I.\n",
        "\n",
        "    Implements exp(-i * c * H_I^approx) for one pair of neighboring sites,\n",
        "    where c = delta_tau * x.\n",
        "    \"\"\"\n",
        "    qc_temp = QuantumCircuit(2)\n",
        "    qc_temp.cx(1, 0)\n",
        "    qc_temp.h(1)\n",
        "    qc_temp.rz(-c, 1)\n",
        "    qc_temp.cx(0, 1)\n",
        "    qc_temp.rz(c, 1)\n",
        "    qc_temp.cx(0, 1)\n",
        "    qc_temp.h(1)\n",
        "    qc_temp.cx(1, 0)\n",
        "    return qc_temp\n",
        "\n",
        "\n",
        "def electric_hamiltonian_circuit(theta: float) -> QuantumCircuit:\n",
        "    \"\"\"Two-qubit unitary for the approximate electric field Hamiltonian H_E.\n",
        "\n",
        "    Implements exp(-i * theta * H_E^approx) for one lattice site,\n",
        "    where theta = -delta_tau * (n_bar_l / 2 + 3/4).\n",
        "    \"\"\"\n",
        "    qc_temp = QuantumCircuit(2)\n",
        "    qc_temp.x(0)\n",
        "    qc_temp.rz(theta / 2, 0)\n",
        "    qc_temp.cx(0, 1)\n",
        "    qc_temp.rz(-theta / 2, 1)\n",
        "    qc_temp.cx(0, 1)\n",
        "    qc_temp.rz(theta / 2, 1)\n",
        "    qc_temp.x(0)\n",
        "    return qc_temp\n",
        "\n",
        "\n",
        "def construct_circuit(\n",
        "    num_lattice_point: int,\n",
        "    num_trotter_steps: int,\n",
        "    c: float,\n",
        "    theta: float,\n",
        "    m: float,\n",
        "    theory: Optional[int] = 2,\n",
        "    barriers: Optional[bool] = False,\n",
        "    measurement: Optional[bool] = False,\n",
        "    add_init_state: Optional[bool] = True,\n",
        "    inverse_mid: Optional[bool] = False,\n",
        ") -> QuantumCircuit:\n",
        "    \"\"\"Construct the full Trotterized time-evolution circuit.\n",
        "\n",
        "    Builds a circuit implementing n Trotter steps of the approximate SU(2)\n",
        "    LSH Hamiltonian evolution. The qubit layout uses a zigzag ordering:\n",
        "    n_i(0), n_i(1), n_o(0), n_o(1), n_i(2), n_i(3), n_o(2), n_o(3), ...\n",
        "    which minimizes the number of SWAP layers needed.\n",
        "\n",
        "    Args:\n",
        "        num_lattice_point: Number of lattice sites\n",
        "        (num_qubits = 2 * num_lattice_point).\n",
        "        num_trotter_steps: Number of Trotter steps.\n",
        "        c: Interaction parameter (delta_tau * x).\n",
        "        theta: Electric field phase parameter.\n",
        "        m: Mass parameter (m_tilde = delta_tau * mu).\n",
        "        theory: 1 for single chain, 2 for SU(2). Default 2.\n",
        "        barriers: Insert barriers between Trotter layers for\n",
        "        visualization.\n",
        "        measurement: Append measurements at the end.\n",
        "        add_init_state: Prepare the half-filled (strong-coupling vacuum)\n",
        "        initial state.\n",
        "        inverse_mid: Swap the central sites\n",
        "        (for differential measurement protocol).\n",
        "    \"\"\"\n",
        "    num_qubits = theory * num_lattice_point\n",
        "    qc = QuantumCircuit(num_qubits)\n",
        "\n",
        "    if num_trotter_steps <= 0:\n",
        "        return qc\n",
        "\n",
        "    # --- Initial state preparation ---\n",
        "    if add_init_state:\n",
        "        i = 1\n",
        "        while i < num_lattice_point:\n",
        "            for j in range(theory):\n",
        "                qc.x(i + j * num_lattice_point)\n",
        "            i = i + 2\n",
        "        if inverse_mid:\n",
        "            mid_lattice_qubits = [num_qubits // 2 - 1, num_qubits // 2]\n",
        "            qc.x(mid_lattice_qubits)\n",
        "    else:\n",
        "        i = 1\n",
        "        while i < num_qubits - 1:\n",
        "            qc.swap(i, i + 1)\n",
        "            i = i + 4\n",
        "\n",
        "    # --- Trotter steps ---\n",
        "    for step in range(num_trotter_steps):\n",
        "        if barriers:\n",
        "            qc.barrier()\n",
        "\n",
        "        # First SWAP layer (skipped at step 0 — absorbed into initial state mapping)\n",
        "        if step > 0:\n",
        "            i = 1\n",
        "            while i < num_qubits - 1:\n",
        "                qc.swap(i, i + 1)\n",
        "                i = i + 4\n",
        "\n",
        "        # First layer of pair interactions\n",
        "        j = 0\n",
        "        while j < num_qubits - 2:\n",
        "            circ = pair_hamiltonian_circuit(c)\n",
        "            qc.compose(circ, [j, j + 1], inplace=True)\n",
        "            j = j + 2\n",
        "        if num_lattice_point % 2 == 0:\n",
        "            circ = pair_hamiltonian_circuit(c)\n",
        "            qc.compose(circ, [j, j + 1], inplace=True)\n",
        "\n",
        "        # Second SWAP layer\n",
        "        i = 1\n",
        "        while i < num_qubits - 1:\n",
        "            qc.swap(i, i + 1)\n",
        "            i = i + theory\n",
        "\n",
        "        # Second layer of pair interactions\n",
        "        j = 2\n",
        "        while j < num_qubits - 3:\n",
        "            circ = pair_hamiltonian_circuit(c)\n",
        "            qc.compose(circ, [j, j + 1], inplace=True)\n",
        "            j = j + 2\n",
        "        if num_lattice_point % 2 != 0:\n",
        "            circ = pair_hamiltonian_circuit(c)\n",
        "            qc.compose(circ, [j, j + 1], inplace=True)\n",
        "\n",
        "        # Third SWAP layer\n",
        "        i = 3\n",
        "        while i < num_qubits - 1:\n",
        "            qc.swap(i, i + 1)\n",
        "            i = i + 2 * theory\n",
        "\n",
        "        # Electric field term\n",
        "        if theta != 0:\n",
        "            e_circ = electric_hamiltonian_circuit(theta)\n",
        "            for j in range(num_lattice_point):\n",
        "                qc.compose(e_circ, [2 * j, 2 * j + 1], inplace=True)\n",
        "\n",
        "        # Mass term: Rz(-m_tilde) for even sites, Rz(m_tilde) for odd sites\n",
        "        for q in range(num_qubits):\n",
        "            if q % 2 == 0:\n",
        "                qc.rz(-1 * m, q)\n",
        "            else:\n",
        "                qc.rz(m, q)\n",
        "\n",
        "    if measurement:\n",
        "        qc.measure_all()\n",
        "\n",
        "    return qc"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "setup-postprocess",
      "metadata": {},
      "outputs": [],
      "source": [
        "def get_probabilities(expval: float):\n",
        "    \"\"\"Convert a Z-expectation value to site occupation probability.\n",
        "\n",
        "    Since <Z> = p(0) - p(1), the occupation probability is p(1) = (1 - <Z>) / 2.\n",
        "    \"\"\"\n",
        "    p1 = round((1 - expval) / 2, 3)\n",
        "    return p1\n",
        "\n",
        "\n",
        "def get_number(expval_data, num_lattice_point):\n",
        "    \"\"\"Convert raw Z-expectation values to staggered fermion number n_f at each site.\n",
        "\n",
        "    n_f(r) = n_i(r) + n_o(r)           for even r\n",
        "    n_f(r) = 2 - [n_i(r) + n_o(r)]     for odd r\n",
        "\n",
        "    The two qubits per site encode (n_i, n_o), and occupation probabilities\n",
        "    give us <n_i> and <n_o>.\n",
        "    \"\"\"\n",
        "    N = []\n",
        "    for expvals in expval_data:\n",
        "        Pstep = [get_probabilities(expval) for expval in expvals]\n",
        "        Nstep = []\n",
        "        for k in range(num_lattice_point):\n",
        "            val = Pstep[2 * k] + Pstep[2 * k + 1]\n",
        "            a = 2 * (k % 2) + (1 - 2 * (k % 2)) * val\n",
        "            Nstep.append(float(a))\n",
        "        N.append(Nstep)\n",
        "    return N\n",
        "\n",
        "\n",
        "def calculate_difference(N, N_mid, num_lattice_point):\n",
        "    \"\"\"Differential measurement protocol: |n_f(meson) - n_f(vacuum)|.\n",
        "\n",
        "    Subtracting the vacuum (SCV) evolution from the meson evolution\n",
        "    isolates the coherent hadron signal from symmetric noise and boundary effects.\n",
        "    \"\"\"\n",
        "    N_diff = []\n",
        "    for i in range(len(N)):\n",
        "        Nstep_diff = []\n",
        "        for j in range(num_lattice_point):\n",
        "            Nstep_diff.append(abs(N[i][j] - N_mid[i][j]))\n",
        "        N_diff.append(Nstep_diff)\n",
        "    return N_diff"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "sim-header",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## 소규모 시뮬레이터 예시\n",
        "\n",
        "먼저, 6개 사이트로 구성된 격자(12 큐비트)를 사용하여 소규모로 워크플로를 시연함으로써, 하드웨어에서 실행하기 전에 회로 구성을 검증하고 물리적 관측량을 파악할 수 있도록 하십시오.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### 1단계: 고전적 입력을 양자 문제에 매핑하기\n",
        "\n",
        "해당 논문( $x = 100$, $m/g = 1$ )에서 연구된 약결합 영역에 해당하는 물리적 매개변수를 정의하십시오. 도출된 회로 매개변수는 다음과 같습니다:\n",
        "\n",
        "* $c = \\delta_\\tau \\cdot x = 0.15$ (상호작용 매개변수)\n",
        "* $\\theta = -\\delta_\\tau (\\bar{n}_l/2 + 3/4) = 0.01$ (전기장 위상)\n",
        "* $\\tilde{m} = \\delta_\\tau \\cdot \\mu = 0.03$ (질량 매개변수)\n",
        "\n",
        "트로터 단계 수마다 **두 가지 회로를** 구성합니다. 하나는 중심에서 메손을 초기화하는 회로(`inverse_mid=True`)이고, 다른 하나는 강결합 진공을 준비하는 회로(`inverse_mid=False`)입니다. 미분 측정 프로토콜은 진공의 진화 과정을 차감하여 하드론 신호를 분리합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "step1-params",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Lattice sites: 6, Qubits: 12\n",
            "Parameters: c=0.15, theta=0.01, m_tilde=0.03\n"
          ]
        }
      ],
      "source": [
        "# Physical / circuit parameters\n",
        "num_lattice_point = 6  # 6 lattice sites -> 12 qubits for SU(2)\n",
        "num_qubits = 2 * num_lattice_point\n",
        "c = 0.15  # delta_tau * x\n",
        "theta = 0.01  # electric field phase\n",
        "m = 0.03  # m_tilde = delta_tau * mu\n",
        "trotter_steps = range(1, 11)  # 10 Trotter steps\n",
        "\n",
        "print(f\"Lattice sites: {num_lattice_point}, Qubits: {num_qubits}\")\n",
        "print(f\"Parameters: c={c}, theta={theta}, m_tilde={m}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "step1-circuits",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Circuit for 1 Trotter step: 12 qubits, depth 26\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/loop-string-hadron-dynamics/extracted-outputs/step1-circuits-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Build circuits: meson initial state and vacuum (SCV) initial state\n",
        "circuits_mid = [\n",
        "    construct_circuit(\n",
        "        num_lattice_point,\n",
        "        d,\n",
        "        c,\n",
        "        theta,\n",
        "        m,\n",
        "        barriers=False,\n",
        "        measurement=False,\n",
        "        add_init_state=True,\n",
        "        inverse_mid=True,\n",
        "    )\n",
        "    for d in trotter_steps\n",
        "]\n",
        "\n",
        "circuits = [\n",
        "    construct_circuit(\n",
        "        num_lattice_point,\n",
        "        d,\n",
        "        c,\n",
        "        theta,\n",
        "        m,\n",
        "        barriers=False,\n",
        "        measurement=False,\n",
        "        add_init_state=True,\n",
        "        inverse_mid=False,\n",
        "    )\n",
        "    for d in trotter_steps\n",
        "]\n",
        "\n",
        "# Visualize a single Trotter step\n",
        "print(\n",
        "    f\"Circuit for 1 Trotter step: {circuits[0].num_qubits} qubits, depth {circuits[0].depth()}\"\n",
        ")\n",
        "circuits[0].draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-header",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### 2단계: 양자 하드웨어 실행을 위해 문제를 최적화한다\n",
        "\n",
        "관측 가능한 양을 정의합니다: 모든 큐비트에 대한 단일 큐비트 $Z$ 측정. $\\langle Z \\rangle$ 에서 각 격자 점 $r$ 에 대한 입직 확률을 추출한 다음, 단계적 페르미온 수 $n_f(r)$ 를 구할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "step2-observables",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Number of observables: 12\n"
          ]
        }
      ],
      "source": [
        "# Z observable on each qubit\n",
        "observables = [\n",
        "    SparsePauliOp(\"I\" * i + \"Z\" + \"I\" * (num_qubits - i - 1))\n",
        "    for i in range(num_qubits)\n",
        "]\n",
        "\n",
        "print(f\"Number of observables: {len(observables)}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-header",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### 3단계: `Qiskit primitives`를 사용하여 실행하기\n",
        "\n",
        "소규모에서 잡음이 없는 정확한 시뮬레이션을 수행하려면 를 사용하십시오 `StatevectorEstimator` .\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "step3-simulate",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Computed expectation values for 10 Trotter steps\n"
          ]
        }
      ],
      "source": [
        "from qiskit.primitives import StatevectorEstimator\n",
        "\n",
        "estimator = StatevectorEstimator()\n",
        "\n",
        "# Run meson circuits\n",
        "pubs_mid = [(circuit, observables) for circuit in circuits_mid]\n",
        "result_mid = estimator.run(pubs_mid).result()\n",
        "\n",
        "# Run vacuum (SCV) circuits\n",
        "pubs = [(circuit, observables) for circuit in circuits]\n",
        "result = estimator.run(pubs).result()\n",
        "\n",
        "# Extract expectation values\n",
        "raw_expvals_mid = [\n",
        "    result_mid[i].data.evs[::-1] for i in range(len(circuits_mid))\n",
        "]\n",
        "raw_expvals = [result[i].data.evs[::-1] for i in range(len(circuits))]\n",
        "\n",
        "print(f\"Computed expectation values for {len(raw_expvals)} Trotter steps\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step4-header",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### 4단계: 후처리를 수행하고 원하는 기존 형식으로 결과를 반환합니다\n",
        "\n",
        "기대값을 엇갈린 페르미온 수 $n_f(r, t)$ 로 변환하고, 미분 측정 프로토콜(메손 $-$ 진공)을 적용하여 하드론 전파 히트맵을 생성합니다. 이는 참고 문헌의 그림 3의 구조를 재현한 것으로, x축에는 격자 점 $r$, y축에는 트로터 단계(시간) $t$, 색상 척도에는 $n_f(r,t)$ 가 표시되어 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "step4-postprocess",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/loop-string-hadron-dynamics/extracted-outputs/step4-postprocess-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Compute fermion numbers\n",
        "N_mid_sim = get_number(raw_expvals_mid, num_lattice_point)\n",
        "N_sim = get_number(raw_expvals, num_lattice_point)\n",
        "N_diff_sim = calculate_difference(N_mid_sim, N_sim, num_lattice_point)\n",
        "\n",
        "# --- Reproduce Figure 3 style: Staggered Fermionic Occupation Number Dynamics ---\n",
        "fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n",
        "\n",
        "# Convert to numpy arrays for plotting\n",
        "N_mid_arr = np.array(N_mid_sim)\n",
        "N_arr = np.array(N_sim)\n",
        "N_diff_arr = np.array(N_diff_sim)\n",
        "\n",
        "# Color scheme\n",
        "vmax = max(max(sublist) for sublist in N_arr)\n",
        "vmin = -vmax\n",
        "\n",
        "# Meson evolution\n",
        "norm1 = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)\n",
        "im0 = axes[0].imshow(\n",
        "    N_mid_arr,\n",
        "    aspect=\"auto\",\n",
        "    origin=\"lower\",\n",
        "    cmap=\"RdBu_r\",\n",
        "    norm=norm1,\n",
        "    extent=[0, num_lattice_point, 0.5, len(trotter_steps) + 0.5],\n",
        ")\n",
        "axes[0].set_xlabel(\"Lattice site $r$\", fontsize=12)\n",
        "axes[0].set_ylabel(\"Trotter step $t$\", fontsize=12)\n",
        "axes[0].set_title(\"$n_f(r,t)$ — Meson initial state\", fontsize=12)\n",
        "plt.colorbar(im0, ax=axes[0], label=\"$n_f(r,t)$\")\n",
        "\n",
        "# Vacuum (SCV) evolution\n",
        "im1 = axes[1].imshow(\n",
        "    N_arr,\n",
        "    aspect=\"auto\",\n",
        "    origin=\"lower\",\n",
        "    cmap=\"RdBu_r\",\n",
        "    norm=norm1,\n",
        "    extent=[0, num_lattice_point, 0.5, len(trotter_steps) + 0.5],\n",
        ")\n",
        "axes[1].set_xlabel(\"Lattice site $r$\", fontsize=12)\n",
        "axes[1].set_ylabel(\"Trotter step $t$\", fontsize=12)\n",
        "axes[1].set_title(\"$n_f(r,t)$ — Vacuum (SCV)\", fontsize=12)\n",
        "plt.colorbar(im1, ax=axes[1], label=\"$n_f(r,t)$\")\n",
        "\n",
        "# Differential: meson - vacuum\n",
        "norm2 = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)\n",
        "im2 = axes[2].imshow(\n",
        "    N_diff_arr,\n",
        "    aspect=\"auto\",\n",
        "    origin=\"lower\",\n",
        "    cmap=\"RdBu_r\",\n",
        "    norm=norm2,\n",
        "    extent=[0, num_lattice_point, 0.5, len(trotter_steps) + 0.5],\n",
        ")\n",
        "axes[2].set_xlabel(\"Lattice site $r$\", fontsize=12)\n",
        "axes[2].set_ylabel(\"Trotter step $t$\", fontsize=12)\n",
        "axes[2].set_title(\n",
        "    \"Staggered Fermionic Occupation Number Dynamics\\n$|n_f^{\\\\mathrm{meson}} - n_f^{\\\\mathrm{vacuum}}|$\",\n",
        "    fontsize=12,\n",
        ")\n",
        "plt.colorbar(im2, ax=axes[2], label=\"$n_f(r,t)$\")\n",
        "\n",
        "plt.suptitle(\n",
        "    f\"Hadron propagation — {num_lattice_point}-site lattice (StatevectorEstimator)\",\n",
        "    fontsize=14,\n",
        "    y=1.02,\n",
        ")\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hardware-header",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## 대규모 하드웨어 예시\n",
        "\n",
        "이제 IBM Quantum 하드웨어에서 30개 사이트 격자(60 큐비트)로 규모를 확장합니다. 이 규모에서, 10 트로터 단계로 구성된 회로는 3,400개 이상의 2-큐비트 게이트와 14,000개의 단일 큐비트 게이트로 이루어져 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hardware-steps",
      "metadata": {},
      "source": [
        "<span id=\"steps-1-4-compressed-into-a-single-code-block\" />\n",
        "\n",
        "### 1\\~4단계 (단일 코드 블록으로 통합됨)\n",
        "\n",
        "하드웨어 워크플로의 주요 측면:\n",
        "\n",
        "* 메손 및 진공 회로를 위한 10개의 트로터 단계 (드리프트를 최소화하기 위해 인터리브 처리됨)\n",
        "* —를 이용한 `optimization_level=1` 트랜스파일레이션: 회로 레이아웃은 이미 소자 토폴로지(선형 체인)와 동형이기 때문에, 라우팅 SWAP이 필요하지 않습니다. 이 트랜스파일러는 물리적 큐비트의 노이즈가 적은 체인을 선택하고, 게이트를 기본 게이트 집합으로 분해하는 데만 사용됩니다.\n",
        "* `EstimatorV2` TREX 판독 오류 완화 및 파울리 회전 기법을 활용하여\n",
        "* `Batch` 모든 작업을 한꺼번에 제출하는 세션\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "hardware-code",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 1: Define parameters & build circuits-------------------------\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit_ibm_runtime import EstimatorV2, Batch\n",
        "from qiskit_ibm_runtime.options import (\n",
        "    EstimatorOptions,\n",
        "    ResilienceOptionsV2,\n",
        "    TwirlingOptions,\n",
        "    DynamicalDecouplingOptions,\n",
        ")\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "num_lattice_point_hw = 30\n",
        "num_qubits_hw = 2 * num_lattice_point_hw  # 60 qubits\n",
        "c_hw = 0.15\n",
        "theta_hw = 0.01\n",
        "m_hw = 0.03\n",
        "trotter_steps_hw = range(1, 11)  # 10 Trotter steps\n",
        "\n",
        "# Build meson and vacuum circuits\n",
        "circuits_mid_hw = [\n",
        "    construct_circuit(\n",
        "        num_lattice_point_hw,\n",
        "        d,\n",
        "        c_hw,\n",
        "        theta_hw,\n",
        "        m_hw,\n",
        "        barriers=False,\n",
        "        measurement=False,\n",
        "        add_init_state=True,\n",
        "        inverse_mid=True,\n",
        "    )\n",
        "    for d in trotter_steps_hw\n",
        "]\n",
        "\n",
        "circuits_hw = [\n",
        "    construct_circuit(\n",
        "        num_lattice_point_hw,\n",
        "        d,\n",
        "        c_hw,\n",
        "        theta_hw,\n",
        "        m_hw,\n",
        "        barriers=False,\n",
        "        measurement=False,\n",
        "        add_init_state=True,\n",
        "        inverse_mid=False,\n",
        "    )\n",
        "    for d in trotter_steps_hw\n",
        "]\n",
        "\n",
        "print(f\"Built {len(circuits_hw)} circuit pairs for {num_qubits_hw} qubits\")\n",
        "\n",
        "# -------------------------Step 2: Transpile for hardware-------------------------\n",
        "# The circuit topology is a linear chain, isomorphic to the device topology.\n",
        "# We use optimization_level=1 since no routing SWAPs are needed — the transpiler\n",
        "# only needs to select a low-noise qubit chain and decompose to native gates.\n",
        "\n",
        "backend = service.backend(\"ibm_boston\")\n",
        "\n",
        "layout = [\n",
        "    140,\n",
        "    141,\n",
        "    142,\n",
        "    143,\n",
        "    136,\n",
        "    123,\n",
        "    122,\n",
        "    121,\n",
        "    116,\n",
        "    101,\n",
        "    102,\n",
        "    103,\n",
        "    96,\n",
        "    83,\n",
        "    82,\n",
        "    81,\n",
        "    76,\n",
        "    61,\n",
        "    62,\n",
        "    63,\n",
        "    64,\n",
        "    65,\n",
        "    66,\n",
        "    67,\n",
        "    68,\n",
        "    69,\n",
        "    78,\n",
        "    89,\n",
        "    88,\n",
        "    87,\n",
        "    97,\n",
        "    107,\n",
        "    106,\n",
        "    105,\n",
        "    117,\n",
        "    125,\n",
        "    126,\n",
        "    127,\n",
        "    137,\n",
        "    147,\n",
        "    148,\n",
        "    149,\n",
        "    150,\n",
        "    151,\n",
        "    152,\n",
        "    153,\n",
        "    154,\n",
        "    155,\n",
        "    139,\n",
        "    135,\n",
        "    134,\n",
        "    133,\n",
        "    132,\n",
        "    131,\n",
        "    130,\n",
        "    129,\n",
        "    118,\n",
        "    109,\n",
        "    110,\n",
        "    111,\n",
        "]\n",
        "\n",
        "\n",
        "pm = generate_preset_pass_manager(\n",
        "    optimization_level=1, backend=backend, initial_layout=layout\n",
        ")\n",
        "\n",
        "isa_circuits_mid = pm.run(circuits_mid_hw)\n",
        "isa_circuits = pm.run(circuits_hw)\n",
        "\n",
        "print(f\"Transpiled circuits. Example depth: {isa_circuits[0].depth()}\")\n",
        "\n",
        "# Define and layout-map observables\n",
        "observables_hw = [\n",
        "    SparsePauliOp(\"I\" * i + \"Z\" + \"I\" * (num_qubits_hw - i - 1))\n",
        "    for i in range(num_qubits_hw)\n",
        "]\n",
        "\n",
        "isa_observables_mid = [\n",
        "    [obs.apply_layout(isa_circuits_mid[i].layout) for obs in observables_hw]\n",
        "    for i in range(len(isa_circuits_mid))\n",
        "]\n",
        "isa_observables = [\n",
        "    [obs.apply_layout(isa_circuits[i].layout) for obs in observables_hw]\n",
        "    for i in range(len(isa_circuits))\n",
        "]\n",
        "\n",
        "# Build PUBs — interleave meson and vacuum for each Trotter step\n",
        "isa_pubs_mid = [\n",
        "    (circ, obs) for circ, obs in zip(isa_circuits_mid, isa_observables_mid)\n",
        "]\n",
        "isa_pubs = [(circ, obs) for circ, obs in zip(isa_circuits, isa_observables)]\n",
        "\n",
        "pubs_to_execute = [\n",
        "    [isa_pubs_mid[i], isa_pubs[i]] for i in range(len(isa_pubs))\n",
        "]\n",
        "\n",
        "# -------------------------Step 3: Execute on hardware-------------------------\n",
        "\n",
        "twirling_options = TwirlingOptions(\n",
        "    enable_gates=True,\n",
        "    enable_measure=True,\n",
        "    shots_per_randomization=\"auto\",\n",
        "    strategy=\"active-circuit\",\n",
        ")\n",
        "\n",
        "resilience_options = ResilienceOptionsV2(\n",
        "    measure_mitigation=True,  # TREX readout error mitigation\n",
        "    zne_mitigation=False,  # ZNE turned off\n",
        ")\n",
        "\n",
        "dd_options = DynamicalDecouplingOptions(\n",
        "    enable=False  # Circuit is sufficiently dense\n",
        ")\n",
        "\n",
        "options = EstimatorOptions(\n",
        "    resilience=resilience_options,\n",
        "    twirling=twirling_options,\n",
        "    dynamical_decoupling=dd_options,\n",
        "    default_shots=10_000,\n",
        ")\n",
        "\n",
        "ids = []\n",
        "with Batch(backend=backend) as batch:\n",
        "    for idx, pub in enumerate(pubs_to_execute):\n",
        "        print(f\"Submitting job for Trotter step {idx + 1}\")\n",
        "        estimator = EstimatorV2(mode=batch, options=options)\n",
        "        estimator.skip_transpilation = True\n",
        "        job = estimator.run(pub)\n",
        "        ids.append(job.job_id())\n",
        "    batch_id = batch.session_id\n",
        "\n",
        "job_info = {\"ids\": ids, \"batch_id\": batch_id}\n",
        "print(f\"Submitted {len(ids)} jobs. Batch ID: {batch_id}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f03fb6e6-2ba7-49bb-b1f5-eb9b6eda993b",
      "metadata": {},
      "outputs": [],
      "source": [
        "print(ids)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "72d09009-e0d2-4bb0-9157-2e88a9d973ea",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 4: Post-process results-------------------------\n",
        "\n",
        "jobs = [service.job(job_id) for job_id in ids]\n",
        "results = [job.result() for job in jobs]\n",
        "\n",
        "# Extract expectation values (index 0 = meson, index 1 = vacuum)\n",
        "raw_expvals_mid_hw = [result[0].data.evs[::-1] for result in results]\n",
        "raw_expvals_hw = [result[1].data.evs[::-1] for result in results]\n",
        "\n",
        "# Compute fermion numbers and differential\n",
        "N_mid_hw = get_number(raw_expvals_mid_hw, num_lattice_point_hw)\n",
        "N_hw = get_number(raw_expvals_hw, num_lattice_point_hw)\n",
        "N_diff_hw = calculate_difference(N_mid_hw, N_hw, num_lattice_point_hw)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "19ee420d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/loop-string-hadron-dynamics/extracted-outputs/19ee420d-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "N_diff_hw_arr = np.array(N_diff_hw)\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(10, 6))\n",
        "vmax = np.abs(N_hw).max()\n",
        "vmin = -vmax\n",
        "norm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)\n",
        "im = ax.imshow(\n",
        "    N_diff_hw_arr,\n",
        "    aspect=\"auto\",\n",
        "    origin=\"lower\",\n",
        "    cmap=\"RdBu_r\",\n",
        "    norm=norm,\n",
        "    extent=[0, 8, 0.5, len(trotter_steps_hw) + 0.5],\n",
        ")\n",
        "ax.set_xlabel(\"Lattice site $r$\", fontsize=13)\n",
        "ax.set_ylabel(\"Trotter step $t$\", fontsize=13)\n",
        "ax.set_title(\n",
        "    \"Staggered Fermionic Occupation Number Dynamics\\nQuantum Simulation on IBM Hardware — 30-site lattice (60 qubits)\",\n",
        "    fontsize=13,\n",
        ")\n",
        "cbar = plt.colorbar(im, ax=ax)\n",
        "cbar.set_label(\"$n_f(r,t)$\", fontsize=12)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "de8e2aa6",
      "metadata": {},
      "source": [
        "<span id=\"classical-benchmarking-via-pauli-propagation\" />\n",
        "\n",
        "## 파울리 전파를 이용한 고전적 벤치마킹\n",
        "\n",
        "파울리 전파법(PPM)은 하이젠베르크 모델에서 측정된 관측량을 회로 전체로 역전파함으로써, 양자 회로에 대한 잡음이 없는 고전적 시뮬레이션을 제공한다. 클리포드 층(CNOT, H, S, X 게이트)에서는 파울리 연산자가 항의 개수를 늘리지 않고 다른 파울리 연산자로 매핑됩니다. 비클리포드 층(회로 내의 $R_z$ 게이트)은 분기를 유발할 수 있으며, 최악의 경우 항의 수가 두 배로 늘어날 수 있지만, 많은 분기들은 계수가 작아 생략할 수 있습니다.\n",
        "\n",
        "...를 사용한 [`pauli-prop`](https://github.com/Qiskit/pauli-prop) 워크플로는 다음과 같습니다:\n",
        "\n",
        "1. `evolve_through_cliffords`...를 사용하여 회로를 클리포드 부분과 비클리포드 부분으로 **나누십시오**.\n",
        "2. `atol``propagate_through_circuit`각 관측량을 를 사용하여 비클리포드 부분에 대해 **전파하되**, 최대 개의 `max_terms` 파울리 항까지 유지하고, 계수가 절단 임계값 보다 작은 항은 제외한다.\n",
        "3. Qiskit에 내장된 클리포드 연산 기능을 사용하여 클리포드 연산을 통해 결과를 **도출합니다**.\n",
        "4. 대각선 파울리 항( $I$ 와 $Z$ 만 포함)의 계수를 합산하여 기대값을 **구한다**.\n",
        "\n",
        "<span id=\"truncation-threshold\" />\n",
        "\n",
        "### 절단 임계값\n",
        "\n",
        "의 `propagate_through_circuit` 매개변수는 `atol` 작은 파울리 분기를 얼마나 적극적으로 제거할지 결정합니다. 매우 엄격한 임계값(예를 들어, `1e-12`)을 적용하면 거의 모든 분기를 유지하여 정확한 결과를 얻을 수 있지만, 회로 깊이가 깊어질수록 시뮬레이션 시간이 급격히 증가합니다. [이](https://arxiv.org/abs/2602.18080) 논문에서 제시된 120-큐비트 시뮬레이션은 기본 설정에서 약 8.5 시간이 소요되었습니다. `1e-3`임계값을 높이면(예를 들어, 또는 로 `1e-6` ), 계수가 해당 값보다 낮은 항들이 제외되어 추적되는 항의 수가 대폭 줄어들고 계산 속도가 빨라집니다. 그 대가로 발생하는 것은 작고 제어 가능한 근사 오차이며, 이는 서로 다른 임계값에서 얻은 결과를 비교함으로써 검증할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "0ed2dd40",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "PPM settings: atol=0.001, max_terms=66000\n",
            "Trotter step  1: 5.0 s\n",
            "Trotter step  2: 7.5 s\n",
            "Trotter step  3: 11.2 s\n",
            "Trotter step  4: 14.7 s\n",
            "Trotter step  5: 18.3 s\n",
            "Trotter step  6: 22.1 s\n",
            "Trotter step  7: 25.6 s\n",
            "Trotter step  8: 29.4 s\n",
            "Trotter step  9: 33.2 s\n",
            "Trotter step 10: 36.6 s\n",
            "\n",
            "Total PPM simulation time: 203.6 s\n",
            "Truncation threshold used: 0.001\n"
          ]
        }
      ],
      "source": [
        "import time\n",
        "from pauli_prop import evolve_through_cliffords, propagate_through_circuit\n",
        "\n",
        "# ── PPM Configuration ──\n",
        "# Truncation threshold: controls the speed/accuracy trade-off.\n",
        "PPM_THRESHOLD = 1e-3\n",
        "\n",
        "# Maximum Pauli terms to track per observable (hard cap on memory/time)\n",
        "PPM_MAX_TERMS = 66_000\n",
        "\n",
        "print(f\"PPM settings: atol={PPM_THRESHOLD}, max_terms={PPM_MAX_TERMS}\")\n",
        "\n",
        "# We propagate each single-qubit Z observable through each circuit.\n",
        "# For PPM, we work with the un-transpiled circuits (ideal noiseless simulation).\n",
        "\n",
        "observables_pp = [\n",
        "    SparsePauliOp(\"I\" * i + \"Z\" + \"I\" * (num_qubits_hw - i - 1))\n",
        "    for i in range(num_qubits_hw)\n",
        "]\n",
        "\n",
        "\n",
        "def ppm_expectation_values(\n",
        "    circuit, observables, max_terms=PPM_MAX_TERMS, atol=PPM_THRESHOLD\n",
        "):\n",
        "    \"\"\"Compute expectation values of single-qubit Z observables\n",
        "    via Pauli propagation.\n",
        "\n",
        "    Args:\n",
        "        circuit: The quantum circuit to simulate.\n",
        "        observables: List of single-qubit Z observables.\n",
        "        max_terms: Maximum number of Pauli terms to retain (hard cap).\n",
        "        atol: Absolute tolerance — Pauli terms with coefficients below this\n",
        "              value are discarded during propagation. Larger values give\n",
        "              faster simulation at the cost of approximation accuracy.\n",
        "    \"\"\"\n",
        "    circuit = circuit.decompose([\"swap\"])  # decompose SWAPs into 3 CX gates\n",
        "    cliff, non_cliff = evolve_through_cliffords(circuit)\n",
        "\n",
        "    evs = []\n",
        "    for obs in observables:\n",
        "        evolved_obs = propagate_through_circuit(\n",
        "            obs, non_cliff, max_terms=max_terms, atol=atol, frame=\"h\"\n",
        "        )[0]\n",
        "        evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame=\"h\")\n",
        "        diagonal_mask = ~evolved_obs.paulis.x.any(axis=1)\n",
        "        ev = float(evolved_obs.coeffs[diagonal_mask].sum().real)\n",
        "        evs.append(ev)\n",
        "    return np.array(evs)\n",
        "\n",
        "\n",
        "# Run PPM for each Trotter step and record wall-clock time\n",
        "pp_expvals_mid = []\n",
        "pp_expvals = []\n",
        "pp_times = []\n",
        "\n",
        "for idx, d in enumerate(trotter_steps_hw):\n",
        "    t_start = time.perf_counter()\n",
        "\n",
        "    # Meson circuit\n",
        "    evs_mid = ppm_expectation_values(circuits_mid_hw[idx], observables_pp)\n",
        "\n",
        "    # Vacuum circuit\n",
        "    evs_vac = ppm_expectation_values(circuits_hw[idx], observables_pp)\n",
        "\n",
        "    elapsed = time.perf_counter() - t_start\n",
        "    pp_times.append(elapsed)\n",
        "\n",
        "    pp_expvals_mid.append(evs_mid[::-1])\n",
        "    pp_expvals.append(evs_vac[::-1])\n",
        "\n",
        "    print(f\"Trotter step {d:2d}: {elapsed:.1f} s\")\n",
        "\n",
        "print(f\"\\nTotal PPM simulation time: {sum(pp_times):.1f} s\")\n",
        "print(f\"Truncation threshold used: {PPM_THRESHOLD}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "pauli-prop-timing-plot",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/loop-string-hadron-dynamics/extracted-outputs/pauli-prop-timing-plot-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# --- PPM simulation time vs. Trotter steps ---\n",
        "fig, ax = plt.subplots(figsize=(8, 5))\n",
        "ax.plot(\n",
        "    list(trotter_steps_hw),\n",
        "    pp_times,\n",
        "    \"o-\",\n",
        "    color=\"tab:blue\",\n",
        "    linewidth=2,\n",
        "    markersize=6,\n",
        ")\n",
        "ax.set_xlabel(\"Trotter step\", fontsize=13)\n",
        "ax.set_ylabel(\"Wall-clock time (s)\", fontsize=13)\n",
        "ax.set_title(\n",
        "    \"Pauli Propagation simulation time vs. Trotter steps\\n(30-site lattice, 60 qubits)\",\n",
        "    fontsize=13,\n",
        ")\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "pauli-prop-heatmap",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/loop-string-hadron-dynamics/extracted-outputs/pauli-prop-heatmap-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# --- PPM heatmap and comparison with hardware ---\n",
        "N_mid_pp = get_number(pp_expvals_mid, num_lattice_point_hw)\n",
        "N_pp = get_number(pp_expvals, num_lattice_point_hw)\n",
        "N_diff_pp = calculate_difference(N_mid_pp, N_pp, num_lattice_point_hw)\n",
        "\n",
        "N_diff_pp_arr = np.array(N_diff_pp)\n",
        "\n",
        "fig, axes = plt.subplots(1, 2, figsize=(18, 6))\n",
        "vmax = np.abs(N_hw).max()\n",
        "vmin = -vmax\n",
        "norm = TwoSlopeNorm(vmin=vmin, vcenter=0, vmax=vmax)\n",
        "\n",
        "# PPM result\n",
        "im0 = axes[0].imshow(\n",
        "    N_diff_pp_arr,\n",
        "    aspect=\"auto\",\n",
        "    origin=\"lower\",\n",
        "    cmap=\"RdBu_r\",\n",
        "    norm=norm,\n",
        "    extent=[0, num_lattice_point_hw, 0.5, len(trotter_steps_hw) + 0.5],\n",
        ")\n",
        "axes[0].set_xlabel(\"Lattice site $r$\", fontsize=12)\n",
        "axes[0].set_ylabel(\"Trotter step $t$\", fontsize=12)\n",
        "axes[0].set_title(\n",
        "    \"Pauli Propagation\\n(classical noiseless simulation)\", fontsize=12\n",
        ")\n",
        "plt.colorbar(im0, ax=axes[0], label=\"$n_f(r,t)$\")\n",
        "\n",
        "# Hardware result\n",
        "im1 = axes[1].imshow(\n",
        "    N_diff_hw_arr,\n",
        "    aspect=\"auto\",\n",
        "    origin=\"lower\",\n",
        "    cmap=\"RdBu_r\",\n",
        "    norm=norm,\n",
        "    extent=[0, num_lattice_point_hw, 0.5, len(trotter_steps_hw) + 0.5],\n",
        ")\n",
        "axes[1].set_xlabel(\"Lattice site $r$\", fontsize=12)\n",
        "axes[1].set_ylabel(\"Trotter step $t$\", fontsize=12)\n",
        "axes[1].set_title(\n",
        "    \"Quantum Simulation\\n(IBM Hardware, readout error mitigation only)\",\n",
        "    fontsize=12,\n",
        ")\n",
        "plt.colorbar(im1, ax=axes[1], label=\"$n_f(r,t)$\")\n",
        "\n",
        "plt.suptitle(\n",
        "    \"Staggered Fermionic Occupation Number Dynamics — 30-site lattice\",\n",
        "    fontsize=14,\n",
        "    y=1.02,\n",
        ")\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "next-steps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 다음 단계\n",
        "\n",
        "이 글이 흥미로웠다면, 다음 자료를 살펴보시는 것도 좋습니다:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"권장사항\">\n",
        "  * [Qiskit Estimator 기본 요소 문서](/docs/guides/get-started-with-estimator) — 오류 완화 옵션 구성에 대한 자세한 내용\n",
        "  * [오류 완화 및 억제 기법](/docs/guides/error-mitigation-and-suppression-techniques) — TREX, ZNE 및 기타 완화 기법에 대해 알아보기\n",
        "  * [Qiskit Pauli Propagation (pauli-prop)](https://github.com/Qiskit/pauli-prop) — Pauli 역전파를 통한 Rust 기반 가속 클래식 시뮬레이션\n",
        "</Admonition>\n",
        "\n",
        "<span id=\"references\" />\n",
        "\n",
        "## 참조\n",
        "\n",
        "\\[1] 원문 논문: Ilčić, Majumdar, Mathew 외. “잡음이 있는 양자 프로세서에서 관찰된 견고하고 일관된 비아벨 하드론 역학” [arXiv:2602.18080](https://arxiv.org/abs/2602.18080) (2026)\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"
    },
    "hours": 1,
    "qpuSeconds": 360
  },
  "nbformat": 4,
  "nbformat_minor": 5
}