{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "b6d1e3ec",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Q-CTRL의 성능 관리를 통한 횡자계 이징 모델\"\n",
        "description: \"Fire Opal과 Qiskit을 사용하여 횡방향장 이징 모델을 구축하고 해결하기\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore fontsize sharey rmse RMSE boxstyle */}\n",
        "\n",
        "<span id=\"transverse-field-ising-model-with-q-ctrls-performance-management\" />\n",
        "\n",
        "# Q-CTRL의 성능 관리를 통한 횡자계 이징 모델\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "a6f69b77",
      "metadata": {},
      "source": [
        "*예상 사용 시간: Heron r2 프로세서에서 2분. (참고: 이는 추정치일 뿐입니다. 런타임은 다를 수 있습니다.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8bf80006",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 배경\n",
        "\n",
        "횡장 필드 아이싱 모델(TFIM)은 양자 자성과 위상 전이를 연구하는 데 중요한 역할을 합니다. 이는 격자 위에 배열된 일련의 스핀을 설명하며, 각 스핀은 이웃 스핀과 상호작용하는 동시에 양자 변동을 일으키는 외부 자기장의 영향을 받습니다.\n",
        "\n",
        "이 모델을 시뮬레이션하는 일반적인 접근 방식은 트로터 분해를 사용하여 시간 진화 연산자를 근사화하여 단일 큐비트 회전과 2큐비트 상호 작용을 번갈아 가며 구성하는 회로를 구성하는 것입니다. 그러나 실제 하드웨어에서 이러한 시뮬레이션은 노이즈와 디코히어런스로 인해 실제 동역학에서 편차가 발생하기 때문에 어려움이 있습니다. 이를 극복하기 위해 Q-CTRL의 Fire Opal 오류 억제 및 성능 관리 도구는 키스킷 함수로 제공됩니다( [Fire Opal 문서](/docs/guides/q-ctrl-performance-management) 참조). Fire Opal은 동적 디커플링, 고급 레이아웃, 라우팅 및 기타 오류 억제 기술을 적용하여 회로 실행을 자동으로 최적화하며, 이 모든 것이 노이즈를 줄이기 위한 것입니다. 이러한 개선으로 하드웨어 결과가 노이즈 없는 시뮬레이션과 더욱 밀접하게 일치하여 더 높은 충실도로 TFIM 자화 역학을 연구할 수 있게 되었습니다.\n",
        "\n",
        "이 튜토리얼에서는 이렇게 하겠습니다:\n",
        "\n",
        "* 연결된 스핀 삼각형의 그래프에 TFIM 해밀턴을 구축합니다\n",
        "* 다양한 깊이의 트로터화 회로로 시간 진화 시뮬레이션\n",
        "* 시간 경과에 따른 단일 큐비트 자화 $\\langle Z_i \\rangle$ 계산 및 시각화\n",
        "* 기준 시뮬레이션과 Q-CTRL의 Fire Opal 성능 관리를 사용한 하드웨어 실행 결과를 비교합니다\n",
        "\n",
        "<span id=\"overview\" />\n",
        "\n",
        "## 개요\n",
        "\n",
        "횡장 아이싱 모델(TFIM)은 양자 상전이의 필수적인 특징을 포착하는 양자 스핀 모델입니다. 해밀턴은 다음과 같이 정의됩니다:\n",
        "\n",
        "$$\n",
        "H = -J \\sum_{i} Z_i Z_{i+1} - h \\sum_{i} X_i\n",
        "$$\n",
        "\n",
        "여기서 $Z_i$ 와 $X_i$ 은 큐비트에 작용하는 폴리 연산자 $i$, $J$ 은 인접한 스핀 사이의 결합 강도, $h$ 은 가로 자기장의 강도입니다. 첫 번째 항은 고전적인 강자성 상호작용을 나타내고, 두 번째 항은 횡단장을 통한 양자 변동을 도입합니다. TFIM 역학을 시뮬레이션하려면 연결된 스핀 삼각형의 커스텀 그래프를 기반으로 RX 및 RZZ 게이트 레이어를 통해 구현된 단일 진화 연산자 $e^{-iHt}$ 의 트로터 분해(Trotter decomposition)를 사용합니다. 이 시뮬레이션은 트로터 단계가 증가함에 따라 자화( $\\langle Z \\rangle$ )가 어떻게 변하는지를 살펴봅니다.\n",
        "\n",
        "제안된 TFIM 구현의 성능은 노이즈 없는 시뮬레이션과 노이즈가 있는 백엔드를 비교하여 평가합니다. Fire Opal의 향상된 실행 및 오류 억제 기능은 실제 하드웨어에서 노이즈의 영향을 완화하는 데 사용되어 $\\langle Z_i \\rangle$ 및 상관자 $\\langle Z_i Z_j \\rangle$ 와 같은 스핀 관측값을 보다 안정적으로 추정할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55b94021",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 요구사항\n",
        "\n",
        "이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:\n",
        "\n",
        "* Qiskit SDK v1.4 또는 이후 버전, [시각화](/docs/api/qiskit/visualization) 지원 기능 포함\n",
        "* Qiskit Runtime v0.40 이상 (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Functions Catalog v0.9.0 (`pip install qiskit-ibm-catalog`)\n",
        "* Fire Opal SDK v9.0.2 이상 (`pip install fire-opal`)\n",
        "* Q-CTRL 비주얼라이저 v8.0.2 이상 (`pip install qctrl-visualizer`)\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7db2e559",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## 설정\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ecaa518f",
      "metadata": {},
      "source": [
        "먼저, [IBM Quantum API 키](http://quantum.cloud.ibm.com/) 를 사용하여 인증하십시오. 그런 다음 다음과 같이 Qiskit 함수를 선택합니다. (이 코드는 사용자가 이미 [계정을](/docs/guides/functions-get-started#install-qiskit-functions-catalog-client) 로컬 환경에 저장해 두었다고 가정합니다.)\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "bc380c46",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "import numpy as np\n",
        "import networkx as nx\n",
        "import matplotlib.pyplot as plt\n",
        "import qctrlvisualizer as qv"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e9d916b6",
      "metadata": {},
      "outputs": [],
      "source": [
        "catalog = QiskitFunctionsCatalog(channel=\"ibm_quantum_platform\")\n",
        "\n",
        "# Access Function\n",
        "perf_mgmt = catalog.load(\"q-ctrl/performance-management\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "988ee237",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "## 1단계: 고전적 입력을 양자 문제에 매핑하기\n",
        "\n",
        "<span id=\"generate-tfim-graph\" />\n",
        "\n",
        "### TFIM 그래프 생성\n",
        "\n",
        "먼저 스핀 격자와 그 사이의 결합을 정의하는 것으로 시작합니다. 이 튜토리얼에서는 선형 체인으로 배열된 연결된 삼각형으로 격자를 구성합니다. 각 삼각형은 폐쇄 루프로 연결된 세 개의 노드로 구성되며, 각 삼각형의 한 노드를 이전 삼각형에 연결하여 체인을 형성합니다.\n",
        "\n",
        "도우미 함수 `connected_triangles_adj_matrix` 는 이 구조에 대한 인접 행렬을 작성합니다. $n$ 삼각형 체인의 경우 결과 그래프에 $2n+1$ 노드가 포함됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "829f1b6d",
      "metadata": {},
      "outputs": [],
      "source": [
        "def connected_triangles_adj_matrix(n):\n",
        "    \"\"\"\n",
        "    Generate the adjacency matrix for 'n' connected triangles in a chain.\n",
        "    \"\"\"\n",
        "    num_nodes = 2 * n + 1\n",
        "    adj_matrix = np.zeros((num_nodes, num_nodes), dtype=int)\n",
        "\n",
        "    for i in range(n):\n",
        "        a, b, c = i * 2, i * 2 + 1, i * 2 + 2  # Nodes of the current triangle\n",
        "\n",
        "        # Connect the three nodes in a triangle\n",
        "        adj_matrix[a, b] = adj_matrix[b, a] = 1\n",
        "        adj_matrix[b, c] = adj_matrix[c, b] = 1\n",
        "        adj_matrix[a, c] = adj_matrix[c, a] = 1\n",
        "\n",
        "        # If not the first triangle, connect to the previous triangle\n",
        "        if i > 0:\n",
        "            adj_matrix[a, a - 1] = adj_matrix[a - 1, a] = 1\n",
        "\n",
        "    return adj_matrix"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "53869b13",
      "metadata": {},
      "source": [
        "방금 정의한 격자를 시각화하기 위해 연결된 삼각형의 사슬을 플로팅하고 각 노드에 레이블을 지정할 수 있습니다. 아래 함수는 선택한 수의 트라이앵글에 대한 그래프를 작성하여 표시합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "2fc293aa",
      "metadata": {},
      "outputs": [],
      "source": [
        "def plot_triangle_chain(n, side=1.0):\n",
        "    \"\"\"\n",
        "    Plot a horizontal chain of n equilateral triangles.\n",
        "    Baseline: even nodes (0,2,4,...,2n) on y=0\n",
        "    Apexes: odd nodes (1,3,5,...,2n-1) above the midpoint.\n",
        "    \"\"\"\n",
        "    # Build graph\n",
        "    A = connected_triangles_adj_matrix(n)\n",
        "    G = nx.from_numpy_array(A)\n",
        "\n",
        "    h = np.sqrt(3) / 2 * side\n",
        "    pos = {}\n",
        "\n",
        "    # Place baseline nodes\n",
        "    for k in range(n + 1):\n",
        "        pos[2 * k] = (k * side, 0.0)\n",
        "\n",
        "    # Place apex nodes\n",
        "    for k in range(n):\n",
        "        x_left = pos[2 * k][0]\n",
        "        x_right = pos[2 * k + 2][0]\n",
        "        pos[2 * k + 1] = ((x_left + x_right) / 2, h)\n",
        "\n",
        "    # Draw\n",
        "    fig, ax = plt.subplots(figsize=(1.5 * n, 2.5))\n",
        "    nx.draw(\n",
        "        G,\n",
        "        pos,\n",
        "        ax=ax,\n",
        "        with_labels=True,\n",
        "        font_size=10,\n",
        "        font_color=\"white\",\n",
        "        node_size=600,\n",
        "        node_color=qv.QCTRL_STYLE_COLORS[0],\n",
        "        edge_color=\"black\",\n",
        "        width=2,\n",
        "    )\n",
        "    ax.set_aspect(\"equal\")\n",
        "    ax.margins(0.2)\n",
        "    plt.show()\n",
        "\n",
        "    return G, pos"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8abc0622",
      "metadata": {},
      "source": [
        "이 튜토리얼에서는 20개의 삼각형 체인을 사용하겠습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "861ab6e3",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transverse-field-ising-model/extracted-outputs/861ab6e3-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "n_triangles = 20\n",
        "n_qubits = 2 * n_triangles + 1\n",
        "plot_triangle_chain(n_triangles, side=1.0)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ac6f36e3",
      "metadata": {},
      "source": [
        "<span id=\"coloring-graph-edges\" />\n",
        "\n",
        "### 그래프 변 채우기\n",
        "\n",
        "스핀-스핀 결합을 구현하려면 겹치지 않는 가장자리를 그룹화하는 것이 유용합니다. 이를 통해 2큐비트 게이트를 병렬로 적용할 수 있습니다. 각 에지에 색상을 할당하여 같은 노드에서 만나는 에지가 서로 다른 그룹에 배치되도록 하는 간단한 에지 색상 지정 절차 [\\[1\\]](#references) 를 통해 이를 수행할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c893b88f",
      "metadata": {},
      "outputs": [],
      "source": [
        "def edge_coloring(graph):\n",
        "    \"\"\"\n",
        "    Takes a NetworkX graph and returns a list of lists\n",
        "    where each inner list contains\n",
        "    the edges assigned the same color.\n",
        "    \"\"\"\n",
        "    line_graph = nx.line_graph(graph)\n",
        "    edge_colors = nx.coloring.greedy_color(line_graph)\n",
        "\n",
        "    color_groups = {}\n",
        "    for edge, color in edge_colors.items():\n",
        "        if color not in color_groups:\n",
        "            color_groups[color] = []\n",
        "        color_groups[color].append(edge)\n",
        "\n",
        "    return list(color_groups.values())"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "b4d480b3",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "## 2단계: 양자 하드웨어 실행을 위한 문제 최적화\n",
        "\n",
        "<span id=\"generate-trotterized-circuits-on-spin-graphs\" />\n",
        "\n",
        "### 스핀 그래프 상에서 Trotterized 회로를 생성한다\n",
        "\n",
        "TFIM의 동역학을 시뮬레이션하기 위해 시간 진화 연산자를 근사화하는 회로를 구성합니다.\n",
        "\n",
        "$$\n",
        "U(t) = e^{-i H t}, \\quad \\text{where} \\quad H = -J \\sum_{\\langle i,j \\rangle} Z_i Z_j - h \\sum_i X_i .\n",
        "$$\n",
        "\n",
        "2차 트로터 분해를 사용합니다:\n",
        "\n",
        "$$\n",
        "e^{-i H \\Delta t} \\approx e^{-i H_X \\Delta t / 2}\\, e^{-i H_Z \\Delta t}\\, e^{-i H_X \\Delta t / 2},\n",
        "$$\n",
        "\n",
        "$H_X = -h \\sum_i X_i$ 및 $H_Z = -J \\sum_{\\langle i,j \\rangle} Z_i Z_j$.\n",
        "\n",
        "* $H_X$ 용어는 `RX` 로테이션의 레이어로 구현됩니다.\n",
        "* $H_Z$ 용어는 인터랙션 그래프의 가장자리를 따라 `RZZ` 게이트 레이어로 구현됩니다.\n",
        "\n",
        "이러한 게이트의 각도는 횡방향 필드 $h$, 결합 상수 $J$, 시간 단계 $\\Delta t$ 에 의해 결정됩니다. 여러 트로터 단계를 쌓아 시스템의 동역학에 근사한 깊이가 증가하는 회로를 생성합니다. `generate_tfim_circ_custom_graph` 및 `trotter_circuits` 함수는 임의의 스핀 상호작용 그래프로부터 트로터라이즈드 양자 회로를 구성합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ebe5c712",
      "metadata": {},
      "outputs": [],
      "source": [
        "def generate_tfim_circ_custom_graph(\n",
        "    steps, h, J, dt, psi0, graph: nx.graph.Graph, meas_basis=\"Z\", mirror=False\n",
        "):\n",
        "    \"\"\"\n",
        "    Generate a second order trotter of the form e^(a+b) ~ e^(b/2) e^a e^(b/2)\n",
        "    for simulating a transverse field ising model:\n",
        "    e^{-i H t} where the Hamiltonian H = -J \\\\sum_i Z_i Z_{i+1} + h \\\\sum_i X_i.\n",
        "\n",
        "    steps: Number of trotter steps\n",
        "    theta_x: Angle for layer of X rotations\n",
        "    theta_zz: Angle for layer of ZZ rotations\n",
        "    theta_x: Angle for second layer of X rotations\n",
        "    J: Coupling between nearest neighbor spins\n",
        "    h: The transverse magnetic field strength\n",
        "    dt: t/total_steps\n",
        "    psi0: initial state (assumed to be prepared in the computational basis).\n",
        "    meas_basis: basis to measure all correlators in\n",
        "\n",
        "    This is a second order trotter of the form e^(a+b) ~ e^(b/2) e^a e^(b/2)\n",
        "    \"\"\"\n",
        "    theta_x = h * dt\n",
        "    theta_zz = -2 * J * dt\n",
        "    nq = graph.number_of_nodes()\n",
        "    color_edges = edge_coloring(graph)\n",
        "    circ = QuantumCircuit(nq, nq)\n",
        "    # Initial state, for typical cases in the computational basis\n",
        "    for i, b in enumerate(psi0):\n",
        "        if b == \"1\":\n",
        "            circ.x(i)\n",
        "    # Trotter steps\n",
        "    for step in range(steps):\n",
        "        for i in range(nq):\n",
        "            circ.rx(theta_x, i)\n",
        "        if mirror:\n",
        "            color_edges = [sublist[::-1] for sublist in color_edges[::-1]]\n",
        "        for edge_list in color_edges:\n",
        "            for edge in edge_list:\n",
        "                circ.rzz(theta_zz, edge[0], edge[1])\n",
        "        for i in range(nq):\n",
        "            circ.rx(theta_x, i)\n",
        "\n",
        "    # some typically used basis rotations\n",
        "    if meas_basis == \"X\":\n",
        "        for b in range(nq):\n",
        "            circ.h(b)\n",
        "    elif meas_basis == \"Y\":\n",
        "        for b in range(nq):\n",
        "            circ.sdg(b)\n",
        "            circ.h(b)\n",
        "\n",
        "    for i in range(nq):\n",
        "        circ.measure(i, i)\n",
        "\n",
        "    return circ\n",
        "\n",
        "\n",
        "def trotter_circuits(G, d_ind_tot, J, h, dt, meas_basis, mirror=True):\n",
        "    \"\"\"\n",
        "    Generates a sequence of Trotterized circuits, each with increasing depth.\n",
        "    Given a spin interaction graph and Hamiltonian parameters, it constructs\n",
        "    a list of circuits with 1 to d_ind_tot Trotter steps\n",
        "\n",
        "    G: Graph defining spin interactions (edges = ZZ couplings)\n",
        "    d_ind_tot: Number of Trotter steps (maximum depth)\n",
        "    J: Coupling between nearest neighboring spins\n",
        "    h: Transverse magnetic field strength\n",
        "    dt: (t / total_steps\n",
        "    meas_basis: Basis to measure all correlators in\n",
        "    mirror: If True, mirror the Trotter layers\n",
        "    \"\"\"\n",
        "    qubit_count = len(G)\n",
        "    circuits = []\n",
        "    psi0 = \"0\" * qubit_count\n",
        "\n",
        "    for steps in range(1, d_ind_tot + 1):\n",
        "        circuits.append(\n",
        "            generate_tfim_circ_custom_graph(\n",
        "                steps, h, J, dt, psi0, G, meas_basis, mirror\n",
        "            )\n",
        "        )\n",
        "    return circuits"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50b94af2",
      "metadata": {},
      "source": [
        "<span id=\"estimate-single-qubit-magnetizations-$langle-z_i-rangle$\" />\n",
        "\n",
        "### 단일 큐비트 자화 추정 $\\langle Z_i \\rangle$\n",
        "\n",
        "모델의 역학을 연구하기 위해 기대값 $\\langle Z_i \\rangle = \\langle \\psi | Z_i | \\psi \\rangle$ 으로 정의된 각 큐비트의 자화를 측정하고자 합니다.\n",
        "\n",
        "시뮬레이션에서는 측정 결과에서 직접 이를 계산할 수 있습니다. `z_expectation` 함수는 비트 문자열 수를 처리하고 선택한 큐비트 인덱스에 대해 $\\langle Z_i \\rangle$ 값을 반환합니다. 실제 하드웨어에서는 `generate_z_observables` 함수를 사용하여 폴리 연산자를 지정하여 동일한 수량을 평가한 다음 백엔드에서 기대값을 계산합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3fc929e0",
      "metadata": {},
      "outputs": [],
      "source": [
        "def z_expectation(counts, index):\n",
        "    \"\"\"\n",
        "    counts: Dict of mitigated bitstrings.\n",
        "    index: Index i in the single operator expectation value < II...Z_i...I >\n",
        "        to be calculated.\n",
        "    return:  < Z_i >\n",
        "    \"\"\"\n",
        "    z_exp = 0\n",
        "    tot = 0\n",
        "    for bitstring, value in counts.items():\n",
        "        bit = int(bitstring[index])\n",
        "        sign = 1\n",
        "        if bit % 2 == 1:\n",
        "            sign = -1\n",
        "        z_exp += sign * value\n",
        "        tot += value\n",
        "\n",
        "    return z_exp / tot"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "c9e79716",
      "metadata": {},
      "outputs": [],
      "source": [
        "def generate_z_observables(nq):\n",
        "    observables = []\n",
        "    for i in range(nq):\n",
        "        pauli_string = \"\".join([\"Z\" if j == i else \"I\" for j in range(nq)])\n",
        "        observables.append(SparsePauliOp(pauli_string))\n",
        "    return observables"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "598d11cf",
      "metadata": {},
      "outputs": [],
      "source": [
        "observables = generate_z_observables(n_qubits)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "397d9b05",
      "metadata": {},
      "source": [
        "이제 트로터화된 회로를 생성하기 위한 파라미터를 정의합니다. 이 튜토리얼에서 격자는 20개의 삼각형이 연결된 체인으로, 41큐비트 시스템에 해당합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "2853d21b",
      "metadata": {},
      "outputs": [],
      "source": [
        "all_circs_mirror = []\n",
        "for num_triangles in [n_triangles]:\n",
        "    for meas_basis in [\"Z\"]:\n",
        "        A = connected_triangles_adj_matrix(num_triangles)\n",
        "        G = nx.from_numpy_array(A)\n",
        "        nq = len(G)\n",
        "        d_ind_tot = 22\n",
        "        dt = 2 * np.pi * 1 / 30 * 0.25\n",
        "        J = 1\n",
        "        h = -7\n",
        "        all_circs_mirror.extend(\n",
        "            trotter_circuits(G, d_ind_tot, J, h, dt, meas_basis, True)\n",
        "        )\n",
        "circs = all_circs_mirror"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a4b0476d",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "## 3단계: `Qiskit primitives` 명령어로 실행합니다\n",
        "\n",
        "<span id=\"run-mps-simulation\" />\n",
        "\n",
        "### MPS 시뮬레이션 실행\n",
        "\n",
        "트로터화 회로 목록은 `matrix_product_state` 시뮬레이터를 사용하여 $4096$ 샷을 임의로 선택하여 실행합니다. MPS 방법은 선택한 결합 치수에 따라 정확도가 결정되는 회로 역학에 대한 효율적인 근사치를 제공합니다. 여기서 고려하는 시스템 크기의 경우, 기본 결합 치수는 자화 역학을 충실도 높게 캡처하기에 충분합니다. 원시 카운트는 정규화되며, 이로부터 각 트로터 단계에서 단일 큐비트 기대값 $\\langle Z_i \\rangle$ 을 계산합니다. 마지막으로 모든 큐비트에 대한 평균을 계산하여 자화가 시간에 따라 어떻게 변화하는지 보여주는 단일 곡선을 얻습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "3709531f",
      "metadata": {},
      "outputs": [],
      "source": [
        "backend_sim = AerSimulator(method=\"matrix_product_state\")\n",
        "\n",
        "\n",
        "def normalize_counts(counts_list, shots):\n",
        "    new_counts_list = []\n",
        "    for counts in counts_list:\n",
        "        a = {k: v / shots for k, v in counts.items()}\n",
        "        new_counts_list.append(a)\n",
        "    return new_counts_list\n",
        "\n",
        "\n",
        "def run_sim(circ_list):\n",
        "    shots = 4096\n",
        "    res = backend_sim.run(circ_list, shots=shots)\n",
        "    normed = normalize_counts(res.result().get_counts(), shots)\n",
        "    return normed\n",
        "\n",
        "\n",
        "sim_counts = run_sim(circs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5895aa46",
      "metadata": {},
      "source": [
        "<span id=\"run-on-hardware\" />\n",
        "\n",
        "### 하드웨어에서 실행\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "4b5a2f87",
      "metadata": {},
      "outputs": [],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.backend(\"ibm_marrakesh\")\n",
        "\n",
        "\n",
        "def run_qiskit(circ_list):\n",
        "    shots = 4096\n",
        "    pm = generate_preset_pass_manager(backend=backend)\n",
        "    isa_circuits = [pm.run(qc) for qc in circ_list]\n",
        "    sampler = Sampler(mode=backend)\n",
        "    res = sampler.run(isa_circuits, shots=shots)\n",
        "    res = [r.data.c.get_counts() for r in res.result()]\n",
        "    normed = normalize_counts(res, shots)\n",
        "    return normed\n",
        "\n",
        "\n",
        "qiskit_counts = run_qiskit(circs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d30269ee",
      "metadata": {},
      "source": [
        "<span id=\"run-on-hardware-with-fire-opal\" />\n",
        "\n",
        "### Fire Opal이 탑재된 하드웨어에서 실행\n",
        "\n",
        "우리는 실제 양자 하드웨어에서 자화 동역학을 평가한다. Fire Opal은 표준 ‘ IBM Quantum ’ 추정기 기본 기능을 확장하여 자동 오류 억제 및 성능 관리 기능을 제공하는 Qiskit 함수를 제공합니다. 우리는 트로터화된 회로를 IBM® 백엔드에 직접 제출하고, Fire Opal이 노이즈를 고려한 실행을 처리합니다.\n",
        "\n",
        "`pubs` 의 목록을 준비하며, 각 항목에는 회로와 해당 Pauli-Z 관측값이 포함되어 있습니다. 이 값은 각 트로터 단계에서 각 큐비트에 대한 기대값 $\\langle Z_i \\rangle$ 을 반환하는 Fire Opal의 추정기 함수에 전달됩니다. 그런 다음 큐비트에 대한 결과를 평균화하여 하드웨어에서 자화 곡선을 얻을 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ca8348c9",
      "metadata": {},
      "outputs": [],
      "source": [
        "backend_name = \"ibm_marrakesh\"\n",
        "estimator_pubs = [(qc, observables) for qc in all_circs_mirror[:]]\n",
        "\n",
        "# Run the circuit using the estimator\n",
        "qctrl_estimator_job = perf_mgmt.run(\n",
        "    primitive=\"estimator\",\n",
        "    pubs=estimator_pubs,\n",
        "    backend_name=backend_name,\n",
        "    options={\"default_shots\": 4096},\n",
        ")\n",
        "\n",
        "result_qctrl = qctrl_estimator_job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aa081308",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "## 4단계: 후처리 수행 및 원하는 클래식 형식으로 결과 반환\n",
        "\n",
        "마지막으로 시뮬레이터의 자화 곡선을 실제 하드웨어에서 얻은 결과와 비교합니다. 두 가지를 나란히 플롯하면 파이어 오팔을 사용한 하드웨어 실행이 트로터 단계의 노이즈 없는 기준선과 얼마나 근접하게 일치하는지 알 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 102,
      "id": "91dd23a5",
      "metadata": {},
      "outputs": [],
      "source": [
        "def make_correlators(test_counts, nq, d_ind_tot):\n",
        "    mz = np.empty((nq, d_ind_tot))\n",
        "    for d_ind in range(d_ind_tot):\n",
        "        counts = test_counts[d_ind]\n",
        "        for i in range(nq):\n",
        "            mz[i, d_ind] = z_expectation(counts, i)\n",
        "    average_z = np.mean(mz, axis=0)\n",
        "    return np.concatenate((np.array([1]), average_z), axis=0)\n",
        "\n",
        "\n",
        "sim_exp = make_correlators(sim_counts[0:22], nq=nq, d_ind_tot=22)\n",
        "qiskit_exp = make_correlators(qiskit_counts[0:22], nq=nq, d_ind_tot=22)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 103,
      "id": "1581f9f0",
      "metadata": {},
      "outputs": [],
      "source": [
        "qctrl_exp = [ev.data.evs for ev in result_qctrl[:]]\n",
        "qctrl_exp_mean = np.concatenate(\n",
        "    (np.array([1]), np.mean(qctrl_exp, axis=1)), axis=0\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "d1f90811",
      "metadata": {},
      "outputs": [],
      "source": [
        "def make_expectations_plot(\n",
        "    sim_z,\n",
        "    depths,\n",
        "    exp_qctrl=None,\n",
        "    exp_qctrl_error=None,\n",
        "    exp_qiskit=None,\n",
        "    exp_qiskit_error=None,\n",
        "    plot_from=0,\n",
        "    plot_upto=23,\n",
        "):\n",
        "    import numpy as np\n",
        "    import matplotlib.pyplot as plt\n",
        "\n",
        "    depth_ticks = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]\n",
        "\n",
        "    d = np.asarray(depths)[plot_from:plot_upto]\n",
        "    sim = np.asarray(sim_z)[plot_from:plot_upto]\n",
        "\n",
        "    qk = (\n",
        "        None\n",
        "        if exp_qiskit is None\n",
        "        else np.asarray(exp_qiskit)[plot_from:plot_upto]\n",
        "    )\n",
        "    qc = (\n",
        "        None\n",
        "        if exp_qctrl is None\n",
        "        else np.asarray(exp_qctrl)[plot_from:plot_upto]\n",
        "    )\n",
        "\n",
        "    qk_err = (\n",
        "        None\n",
        "        if exp_qiskit_error is None\n",
        "        else np.asarray(exp_qiskit_error)[plot_from:plot_upto]\n",
        "    )\n",
        "    qc_err = (\n",
        "        None\n",
        "        if exp_qctrl_error is None\n",
        "        else np.asarray(exp_qctrl_error)[plot_from:plot_upto]\n",
        "    )\n",
        "\n",
        "    # ---- helper(s) ----\n",
        "    def rmse(a, b):\n",
        "        if a is None or b is None:\n",
        "            return None\n",
        "        a = np.asarray(a, dtype=float)\n",
        "        b = np.asarray(b, dtype=float)\n",
        "        mask = np.isfinite(a) & np.isfinite(b)\n",
        "        if not np.any(mask):\n",
        "            return None\n",
        "        diff = a[mask] - b[mask]\n",
        "        return float(np.sqrt(np.mean(diff**2)))\n",
        "\n",
        "    def plot_panel(ax, method_y, method_err, color, label, band_color=None):\n",
        "        # Noiseless reference\n",
        "        ax.plot(d, sim, color=\"grey\", label=\"Noiseless simulation\")\n",
        "\n",
        "        # Method line + band\n",
        "        if method_y is not None:\n",
        "            ax.plot(d, method_y, color=color, label=label)\n",
        "            if method_err is not None:\n",
        "                lo = np.clip(method_y - method_err, -1.05, 1.05)\n",
        "                hi = np.clip(method_y + method_err, -1.05, 1.05)\n",
        "                ax.fill_between(\n",
        "                    d,\n",
        "                    lo,\n",
        "                    hi,\n",
        "                    alpha=0.18,\n",
        "                    color=band_color if band_color else color,\n",
        "                    label=f\"{label} ± error\",\n",
        "                )\n",
        "        else:\n",
        "            ax.text(\n",
        "                0.5,\n",
        "                0.5,\n",
        "                \"No data\",\n",
        "                transform=ax.transAxes,\n",
        "                ha=\"center\",\n",
        "                va=\"center\",\n",
        "                fontsize=10,\n",
        "                color=\"0.4\",\n",
        "            )\n",
        "\n",
        "        # RMSE box (vs sim)\n",
        "        r = rmse(method_y, sim)\n",
        "        if r is not None:\n",
        "            ax.text(\n",
        "                0.98,\n",
        "                0.02,\n",
        "                f\"RMSE: {r:.4f}\",\n",
        "                transform=ax.transAxes,\n",
        "                va=\"bottom\",\n",
        "                ha=\"right\",\n",
        "                fontsize=8,\n",
        "                bbox=dict(\n",
        "                    boxstyle=\"round,pad=0.35\", fc=\"white\", ec=\"0.7\", alpha=0.9\n",
        "                ),\n",
        "            )\n",
        "        # Axes\n",
        "        ax.set_xticks(depth_ticks)\n",
        "        ax.set_ylim(-1.05, 1.05)\n",
        "        ax.grid(True, which=\"both\", linewidth=0.4, alpha=0.4)\n",
        "        ax.set_axisbelow(True)\n",
        "        ax.legend(prop={\"size\": 8}, loc=\"best\")\n",
        "\n",
        "    fig, axes = plt.subplots(1, 2, figsize=(10, 4), dpi=300, sharey=True)\n",
        "\n",
        "    axes[0].set_title(\"Fire Opal (Q-CTRL)\", fontsize=10)\n",
        "    plot_panel(\n",
        "        axes[0],\n",
        "        qc,\n",
        "        qc_err,\n",
        "        color=\"#680CE9\",\n",
        "        label=\"Fire Opal\",\n",
        "        band_color=\"#680CE9\",\n",
        "    )\n",
        "    axes[0].set_xlabel(\"Trotter step\")\n",
        "    axes[0].set_ylabel(r\"$\\langle Z \\rangle$\")\n",
        "    axes[1].set_title(\"Qiskit\", fontsize=10)\n",
        "    plot_panel(\n",
        "        axes[1], qk, qk_err, color=\"blue\", label=\"Qiskit\", band_color=\"blue\"\n",
        "    )\n",
        "    axes[1].set_xlabel(\"Trotter step\")\n",
        "\n",
        "    plt.tight_layout()\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "415760ad",
      "metadata": {},
      "outputs": [],
      "source": [
        "depths = list(range(d_ind_tot + 1))\n",
        "errors = np.abs(np.array(qctrl_exp_mean) - np.array(sim_exp))\n",
        "\n",
        "errors_qiskit = np.abs(np.array(qiskit_exp) - np.array(sim_exp))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "d4902d14",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transverse-field-ising-model/extracted-outputs/d4902d14-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "make_expectations_plot(\n",
        "    sim_exp,\n",
        "    depths,\n",
        "    exp_qctrl=qctrl_exp_mean,\n",
        "    exp_qctrl_error=errors,\n",
        "    exp_qiskit=qiskit_exp,\n",
        "    exp_qiskit_error=errors_qiskit,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ee41a301",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## 참조\n",
        "\n",
        "\\[1] 그래프 색칠하기. Wikipedia. 검색된 날짜: 2025년 9월 15일 [https://en.wikipedia.org/wiki/Graph \\_coloring](https://en.wikipedia.org/wiki/Graph_coloring)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2cb5785c",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## 튜토리얼 설문조사\n",
        "\n",
        "잠시 시간을 내어 이 튜토리얼에 대한 피드백을 제공해 주세요. 여러분의 인사이트는 콘텐츠 제공과 사용자 경험을 개선하는 데 도움이 됩니다.\n",
        "\n",
        "[설문조사 링크](https://your.feedback.ibm.com/jfe/form/SV_3BLFkNVEuh0QBWm)\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": 120
  },
  "nbformat": 4,
  "nbformat_minor": 5
}