{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "1ed79d34-1831-47cb-9985-5bb7dacc70e0",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"SABRE를 활용한 트랜스파일레이션 최적화\"\n",
        "description: \"대규모 하드웨어 실행을 위해 SABRE 레이아웃 및 라우팅을 활용하여 양자 회로를 최적화합니다.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore ylabel xlabel fontsize sharex edgecolor, fontweight, elinewidth, ecolor, prerouter, ylim */}\n",
        "\n",
        "<span id=\"transpilation-optimization-with-sabre\" />\n",
        "\n",
        "# SABRE를 활용한 트랜스파일레이션 최적화\n",
        "\n",
        "*예상 소요 시간: Heron r2 프로세서에서 1분 (참고: 이는 단지 예상치일 뿐입니다.) (실행 시간은 다를 수 있습니다.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d6834016-1525-42d7-aa21-0fef9d957ecd",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 학습 성과\n",
        "\n",
        "* 트랜스파일링 품질을 높이기 위해 SABRE 매개변수 (`layout_trials`, `swap_trials`, `max_iterations`)를 설정하는 방법\n",
        "* 트랜스파일레이션 실행 시간과 회로 품질(깊이 및 게이트 수) 간의 상충 관계\n",
        "* SABRE 라우팅 휴리스틱(`basic`, `decay`, `lookahead`)을 사용자 정의하는 방법 및 하드웨어에서 각 휴리스틱의 성능을 비교하는 방법\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 전제조건\n",
        "\n",
        "* [회로 트랜스파일링](/docs/guides/transpile) : Qiskit에서의 트랜스파일링 개요\n",
        "* [트랜스파일러 단계](/docs/guides/transpiler-stages) : 레이아웃 및 라우팅 단계\n",
        "* [사전 설정된 패스 관리자 구성](/docs/guides/transpile-with-pass-managers) : 최적화 수준 사용자 지정\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## 배경\n",
        "\n",
        "트랜스파일레이션은 양자 회로를 특정 양자 하드웨어와 호환되는 형태로 변환하는 과정입니다. 두 가지 핵심 단계는 **큐비트 레이아웃** 선택(논리 큐비트를 물리적 큐비트에 매핑하는 것)과 **게이트 라우팅** (다중 큐비트 게이트가 소자의 연결성을 준수하도록 SWAP 게이트를 삽입하는 것)입니다.\n",
        "\n",
        "**SABRE** ( *SWAP 기반 양방향 휴리스틱 탐색 알고리즘* )는 레이아웃과 라우팅을 모두 최적화합니다. 이는 특히 IBM® 헤론(Heron) 프로세서와 같이 복잡한 결합 맵을 가진 장치에서 대규모 회로(100개 이상의 큐비트)에 특히 효과적입니다. SABRE는 SWAP 게이트 수를 최소화하고 회로 깊이를 줄여 실행 정확도를 향상시킵니다. [LightSABRE](https://arxiv.org/abs/2409.08368) 알고리즘의 최근 개선 사항으로 인해 실행 시간과 게이트 수가 더욱 줄어들었습니다.\n",
        "\n",
        "이 튜토리얼에서는 먼저 다양한 매개변수를 사용하여 소규모 GHZ 회로를 최적화하고, 이것이 실행 정확도에 미치는 영향을 확인해 보겠습니다 `SabreLayout` . 그런 다음, 실제 하드웨어 환경에서 대규모로 SABRE의 경로 탐색 휴리스틱을 비교하게 됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50cf9d9f-c875-49d5-83b0-c691363615ab",
      "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",
        "* Qiskit Aer (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6d9ad2c3-1b10-4a22-8b75-608869be0ac7",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## 설정\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6d93c123-b92e-434d-8361-8115509e6d5f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Using backend: ibm_kingston\n"
          ]
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import EstimatorOptions\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
        "from qiskit_aer.primitives import EstimatorV2 as AerEstimator\n",
        "from qiskit.transpiler.passes import (\n",
        "    SabreLayout,\n",
        "    SabreSwap,\n",
        "    BarrierBeforeFinalMeasurements,\n",
        "    StarPreRouting,\n",
        ")\n",
        "from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit.passmanager.flow_controllers import ConditionalController\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import time\n",
        "\n",
        "seed = 42\n",
        "\n",
        "service = QiskitRuntimeService(\n",
        "    channel=\"ibm_cloud\",\n",
        "    token=\"<YOUR_API_TOKEN>\",  # Replace with your actual API token\n",
        "    instance=\"<YOUR_INSTANCE_NAME>\",  # Replace with your instance name if needed\n",
        ")\n",
        "backend = service.least_busy(operational=True, simulator=False)\n",
        "\n",
        "\n",
        "print(f\"Using backend: {backend.name}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c34842c6-5e1b-4658-8e91-15149280c783",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## 소규모 시뮬레이터 예시\n",
        "\n",
        "이 섹션에서는 실제 백엔드의 노이즈 모델을 기반으로 한 **노이즈 시뮬레이터를** 사용하여, 다양한 `SabreLayout` 구성이 트랜스파일링 품질과 실행 정확도에 어떤 영향을 미치는지 보여줍니다. 실제 하드웨어 보정 데이터에서 도출된 노이즈 모델을 사용하면 `qiskit_aer` 하드웨어 크레딧을 소모하지 않고도 트랜스파일링을 테스트할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a2bcc2b2-727b-447f-b680-a23e3b9e0c3c",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### 1단계: 고전적 입력을 양자 문제에 매핑하기\n",
        "\n",
        "우리는 15개의 큐비트로 구성된 **별형 토폴로지의 GHZ** 회로를 구축한다. 첫 번째 큐비트는 허브 역할을 하며, CNOT 게이트를 통해 다른 모든 큐비트와 직접 연결되어 있습니다. 이 토폴로지는 장치의 결합 맵과 간단히 대응되지 않기 때문에 까다로운 레이아웃 문제를 야기합니다.\n",
        "\n",
        "또한 큐비트 쌍 간의 얽힘 상관관계 $\\langle Z_0 Z_i \\rangle$ 를 측정하기 위한 연산자들도 정의합니다 `ZZ` .\n",
        "\n",
        "![ghz\\_star\\_topology.png](https://quantum.cloud.ibm.com/docs/images/tutorials/transpilation-optimizations-with-sabre/ghz_star_topology.avif)\n",
        "\n",
        "<Admonition type=\"note\" title=\"회로 구조를 파악했다면\">\n",
        "  SABRE는 범용 알고리즘이며, 회로 구조에 대해 어떠한 가정도 하지 않습니다. 이 스타 토폴로지 GHZ 회로의 경우, 실제로 최적의 라우팅 방법이 알려져 있습니다. ‘패스(pass)’는 [`StarPreRouting`](/docs/api/qiskit/qiskit.transpiler.passes.StarPreRouting) 스타 형태의 하위 회로를 감지하여, 충분히 긴 선형 경로를 가진 임의의 백엔드에 직접 매핑되는 선형 체인으로 재구성합니다. 이 튜토리얼에서는 임의의 회로에 적용할 수 있는 SABRE에 중점을 두고 있지만, 회로에 뚜렷한 특수 구조가 있다는 것을 알고 있다면, 라우팅 전에 와 `StarPreRouting` 같은 특화된 패스를 적용하는 것이 어떤 휴리스틱 검색보다 더 나은 성능을 낼 수 있습니다.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "08a8c4df-b1a5-45b7-9808-ca4e5d7631d8",
      "metadata": {},
      "outputs": [],
      "source": [
        "num_qubits_sim = 15\n",
        "\n",
        "# Create star-topology GHZ circuit\n",
        "qc_sim = QuantumCircuit(num_qubits_sim)\n",
        "qc_sim.h(0)\n",
        "for i in range(1, num_qubits_sim):\n",
        "    qc_sim.cx(0, i)\n",
        "qc_sim.measure_all()\n",
        "\n",
        "# ZZ operators: Z on qubit 0 and qubit i, identity elsewhere\n",
        "operator_strings_sim = [\n",
        "    \"Z\" + \"I\" * i + \"Z\" + \"I\" * (num_qubits_sim - 2 - i)\n",
        "    for i in range(num_qubits_sim - 1)\n",
        "]\n",
        "operators_sim = [SparsePauliOp(op) for op in operator_strings_sim]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "25ac8d92-6362-4d4e-a133-2914703df658",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### 2단계: 양자 하드웨어 실행을 위한 문제 최적화\n",
        "\n",
        "`SabreLayout`기본 `optimization_level=3` 프리셋 패스 관리자는 이미 이를 사용하고 있지만, 기본값은 보수적으로 설정되어 있습니다. 더 강력한 설정의 영향을 파악하기 위해, 해당 패스는 보다 공격적인 검색이 이루어지도록 맞춤 `SabreLayout` 구성된 패스로 대체되며, 레이아웃 단계의 다른 모든 패스는 변경되지 않은 상태로 유지됩니다. 별도의 비교 사항으로, 네 번째 패스 관리자는 기본 `SabreLayout` 설정을 유지하되 초기화 단계에 를 추가합니다 [`StarPreRouting`](/docs/api/qiskit/qiskit.transpiler.passes.StarPreRouting) . `StarPreRouting` 라우팅 전에 스타형 하위 회로를 감지하여 이를 선형 체인으로 재작성하는, *구조를* 고려한 패스입니다.\n",
        "\n",
        "작업 흐름은 다음과 같습니다:\n",
        "\n",
        "1. 기본 패스 관리자를 **살펴보고**, 가 단계 `layout` 내에서 어디에 `SabreLayout` 위치하는지 확인하십시오.\n",
        "2. `pm.init += StarPreRouting()``PassManager.replace(index, passes=...)`해당 패스를 를 사용하여 사용자 `SabreLayout` 정의 인스턴스로 **대체하고**, 를 사용하여 변형을 `pm_star` 빌드하십시오.\n",
        "3. 네 가지 패스 관리자를 모두 **실행하고** 지표를 비교해 보세요.\n",
        "\n",
        "네 가지 구성은 다음과 같습니다:\n",
        "\n",
        "| 구성          | 설명                                                                                         |\n",
        "| ----------- | ------------------------------------------------------------------------------------------ |\n",
        "| `pm_1`(기본값) | level-3 의 기본 프리셋 (`SabreLayout` , `max_iterations=4` `layout_trials=20`, `swap_trials=20`) |\n",
        "| `pm_2`      | 사용자 지정 `SabreLayout` (`max_iterations=4`, `layout_trials=200`, `swap_trials=200`)          |\n",
        "| `pm_3`      | 사용자 지정 `SabreLayout` (`max_iterations=8`, `layout_trials=200`, `swap_trials=200`)          |\n",
        "| `pm_star`   | 초기화 단계에 추가된 `StarPreRouting` 기본 사전 설정                                                      |\n",
        "\n",
        "**SABRE의 주요 매개변수:**\n",
        "\n",
        "* **`layout_trials` / `swap_trials`**: SABRE가 탐색할 레이아웃 후보 및 경로 솔루션의 수를 제어합니다. 시도 횟수를 늘리면 SABRE가 더 넓은 탐색 공간을 탐색하게 되어, 더 나은 해법을 찾을 가능성이 높아집니다.\n",
        "* **`max_iterations`**: SABRE가 각 후보에 대해 수행하는 전후 라우팅 정밀화 사이클의 횟수를 제어합니다. SABRE는 라우팅 피드백을 학습하여 레이아웃을 반복적으로 개선하므로, 반복 횟수가 많을수록 개선 효과도 더 커집니다.\n",
        "\n",
        "두 방법 모두 트랜스파일링 시간이 길어지는 단점이 있지만, 그 결과로 생성된 회로는 더 짧고 게이트 수가 적어, 실제 하드웨어에서 발생하는 비고전성 및 게이트 오류를 직접적으로 줄여줍니다.\n",
        "\n",
        "**2a: 단계: 기본 패스 관리자를 확인합니다.** `PassManager`A는 `StagedPassManager` 단계들 (`init`, `layout`, `routing`, `translation`, `optimization`, `scheduling`)로 구성되어 있으며, 각 단계는 그 자체로 a이다. 무대에서 호출하면 `.draw()` 그 패스가 그래프로 표현되므로, 가 어디에 `SabreLayout` 있는지 확인할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "b40fe1e0-41cd-4e8b-acb9-801872d35f1f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/b40fe1e0-41cd-4e8b-acb9-801872d35f1f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Build the default pass manager (no modifications yet)\n",
        "pm_1 = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend, seed_transpiler=seed\n",
        ")\n",
        "\n",
        "# Visualize the layout stage to see where SabreLayout sits\n",
        "pm_1.layout.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f9e5393-af3b-4d3d-a0d8-d20585b02895",
      "metadata": {},
      "source": [
        "`ConditionalController` 위의 다이어그램에서, 우리가 사용자 정의하고자 하는 패스는 `SabreLayout` 레이아웃 단계의 **\\[2번]** 위치에 있습니다. 해당 컨트롤러는 두 가지 기능을 수행합니다:\n",
        "\n",
        "* 게이트 `SabreLayout` 기능이 있어, \\[1] 에서 완벽한 매핑을 `VF2Layout` 찾지 못한 경우에만 실행됩니다(그렇지 않은 경우에는 완벽한 VF2 레이아웃이 유지됩니다).\n",
        "* SabreLayout's 의 내부 라우팅 과정에서 측정값의 순서가 바뀌는 것을 방지하는 패스를 `BarrierBeforeFinalMeasurements` 먼저 수행합니다 `SabreLayout` .\n",
        "\n",
        "만약 우리가 그냥 `replace(index=2, passes=sl_2)`, 두 행동 모두 중단됩니다. 이를 보존하기 위해, 교체하기 전에 맞춤형 `SabreLayout` 제품을 동일한 `ConditionalController` 포장재(동일한 상태와 보호막을 유지한 채)로 다시 포장합니다.\n",
        "\n",
        "**2b: 단계: 사용자 지정 `SabreLayout` 패스를 생성하고 기본값을 대체합니다.**\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "79075a21-8f36-4fd9-9d0d-bd0e97395b60",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/79075a21-8f36-4fd9-9d0d-bd0e97395b60-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "cmap = backend.coupling_map\n",
        "\n",
        "# Custom SabreLayout passes with more aggressive search\n",
        "sl_2 = SabreLayout(\n",
        "    coupling_map=cmap,\n",
        "    seed=seed,\n",
        "    max_iterations=4,\n",
        "    layout_trials=200,\n",
        "    swap_trials=200,\n",
        ")\n",
        "sl_3 = SabreLayout(\n",
        "    coupling_map=cmap,\n",
        "    seed=seed,\n",
        "    max_iterations=8,\n",
        "    layout_trials=200,\n",
        "    swap_trials=200,\n",
        ")\n",
        "\n",
        "\n",
        "# Same condition the preset uses: only run SabreLayout when VF2Layout did not\n",
        "# find a perfect mapping. This preserves any perfect layout VF2 produced at [1].\n",
        "def _vf2_match_not_found(property_set):\n",
        "    if property_set[\"layout\"] is None:\n",
        "        return True\n",
        "    return (\n",
        "        property_set[\"VF2Layout_stop_reason\"] is not None\n",
        "        and property_set[\"VF2Layout_stop_reason\"]\n",
        "        is not VF2LayoutStopReason.SOLUTION_FOUND\n",
        "    )\n",
        "\n",
        "\n",
        "def wrap_sabre(sabre_pass):\n",
        "    \"\"\"Re-wrap a SabreLayout in the original ConditionalController + barrier.\"\"\"\n",
        "    return ConditionalController(\n",
        "        [\n",
        "            BarrierBeforeFinalMeasurements(\n",
        "                \"qiskit.transpiler.internal.routing.protection.barrier\"\n",
        "            ),\n",
        "            sabre_pass,\n",
        "        ],\n",
        "        condition=_vf2_match_not_found,\n",
        "    )\n",
        "\n",
        "\n",
        "# Build two fresh pass managers and swap in the wrapped custom SabreLayout at index 2\n",
        "pm_2 = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend, seed_transpiler=seed\n",
        ")\n",
        "pm_3 = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend, seed_transpiler=seed\n",
        ")\n",
        "pm_2.layout.replace(index=2, passes=wrap_sabre(sl_2))\n",
        "pm_3.layout.replace(index=2, passes=wrap_sabre(sl_3))\n",
        "\n",
        "# Build pm_star: default preset with StarPreRouting added to the init stage\n",
        "pm_star = generate_preset_pass_manager(\n",
        "    optimization_level=3, backend=backend, seed_transpiler=seed\n",
        ")\n",
        "pm_star.init += StarPreRouting()\n",
        "\n",
        "# Visualize pm_3 after replacement (pm_2 has the same structure, only max_iterations differs)\n",
        "pm_3.layout.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "378a5829-b18b-4359-827f-b052c7cc1b89",
      "metadata": {},
      "source": [
        "`max_iterations=4``pm_3`위치 **\\[2는]** 이제 다시 가 `ConditionalController` 되었습니다. 모양은 기본값과 동일하지만, 내부의 `SabreLayout` 는 우리가 직접 만든 것입니다(에서 `layout_trials=200`, `swap_trials=200`, 및 `max_iterations=8` 를 사용하며, `pm_2` 는 를 제외하고는 동일합니다). `pm_2`보호 장벽과 게이트 구조는 `_vf2_match_not_found` 그대로 유지되므로, /와 `pm_1``pm_3` 의 유일한 차이점은 SABRE 구성 자체뿐입니다. `pm_star` 기본값을 `SabreLayout` 유지하며, 초기화 단계의 마지막에만 를 추가합니다 `StarPreRouting` .\n",
        "\n",
        "**2c: 단계 각 패스 매니저를 실행하고 결과를 비교합니다.**\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "fd52f8dd-862b-46e2-b93d-0b35f47a3d83",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "pm_1 (4,20,20): 2Q Depth 38, Size 183, Time 0.01s\n",
            "pm_2 (4,200,200): 2Q Depth 36, Size 183, Time 0.15s\n",
            "pm_3 (8,200,200): 2Q Depth 30, Size 158, Time 0.16s\n",
            "pm_star (default + StarPreRouting): 2Q Depth 26, Size 160, Time 0.01s\n",
            "\n",
            "Improvement vs. default (pm_1):\n",
            "  pm_2 (4,200,200): 2Q depth +5.3%, size +0.0%\n",
            "  pm_3 (8,200,200): 2Q depth +21.1%, size +13.7%\n",
            "  pm_star (default + StarPreRouting): 2Q depth +31.6%, size +12.6%\n"
          ]
        }
      ],
      "source": [
        "results_sim = {}\n",
        "for name, pm in [\n",
        "    (\"pm_1 (4,20,20)\", pm_1),\n",
        "    (\"pm_2 (4,200,200)\", pm_2),\n",
        "    (\"pm_3 (8,200,200)\", pm_3),\n",
        "    (\"pm_star (default + StarPreRouting)\", pm_star),\n",
        "]:\n",
        "    t0 = time.time()\n",
        "    tqc = pm.run(qc_sim)\n",
        "    elapsed = time.time() - t0\n",
        "    depth = tqc.depth(lambda x: x.operation.num_qubits == 2)\n",
        "    size = tqc.size()\n",
        "    ops_mapped = [op.apply_layout(tqc.layout) for op in operators_sim]\n",
        "    results_sim[name] = {\n",
        "        \"tqc\": tqc,\n",
        "        \"ops\": ops_mapped,\n",
        "        \"depth\": depth,\n",
        "        \"size\": size,\n",
        "        \"time\": elapsed,\n",
        "    }\n",
        "    print(f\"{name}: 2Q Depth {depth}, Size {size}, Time {elapsed:.2f}s\")\n",
        "\n",
        "# Print improvement relative to default (pm_1)\n",
        "baseline = results_sim[\"pm_1 (4,20,20)\"]\n",
        "print(\"\\nImprovement vs. default (pm_1):\")\n",
        "for name in [\n",
        "    \"pm_2 (4,200,200)\",\n",
        "    \"pm_3 (8,200,200)\",\n",
        "    \"pm_star (default + StarPreRouting)\",\n",
        "]:\n",
        "    r = results_sim[name]\n",
        "    depth_pct = (baseline[\"depth\"] - r[\"depth\"]) / baseline[\"depth\"] * 100\n",
        "    size_pct = (baseline[\"size\"] - r[\"size\"]) / baseline[\"size\"] * 100\n",
        "    print(f\"  {name}: 2Q depth {depth_pct:+.1f}%, size {size_pct:+.1f}%\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c27cbdb-c065-4a36-a0ef-6c9572c7a4a5",
      "metadata": {},
      "source": [
        "수정된 세 가지 패스 매니저 모두 기본값보다 2Q 깊이가 더 낮은 회로를 생성했다. 공격적인 SABRE 구성(`pm_2` 및 `pm_3`)은 더 넓은 검색 범위를 확보하기 위해 더 긴 변환 시간을 감수하는 반면 `pm_star` , 는 회로의 스타 구조를 활용하여 추가적인 변환 비용 없이 훨씬 더 얕은 결과를 산출합니다. 구체적인 성능 향상 폭은 실행마다 달라지겠지만, 전반적인 추세는 일관됩니다. SABRE 시뮬레이션과 반복 횟수가 늘어날수록 휴리스틱 검색이 더 넓은 영역을 탐색할 수 있게 되며, 회로 형태가 일치할 경우 와 `StarPreRouting` 같은 구조 인식 패스를 통해 해당 검색 과정을 완전히 건너뛸 수 있습니다.\n",
        "\n",
        "이처럼 작은 규모(15 큐비트)에서도 개선의 여지가 충분히 있어, 세 가지 접근법 모두 기본 성능을 능가합니다. 회로 규모가 커질수록(100 큐비트 이상), 탐색 공간이 급격히 확대되며, 대규모 회로 섹션에서 살펴보게 될 것처럼 시도 횟수 증가와 구조 인식 패스의 이점이 훨씬 더 두드러지게 나타납니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "bf75c45a-2c3e-4ef6-8336-0b3f69e6e8fb",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/bf75c45a-2c3e-4ef6-8336-0b3f69e6e8fb-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "pm_names = list(results_sim.keys())\n",
        "depths = [results_sim[n][\"depth\"] for n in pm_names]\n",
        "sizes = [results_sim[n][\"size\"] for n in pm_names]\n",
        "times = [results_sim[n][\"time\"] for n in pm_names]\n",
        "colors = [\"#404080\", \"#2a9d8f\", \"#a8d05e\", \"#e29bdd\"]\n",
        "x = np.arange(len(pm_names))\n",
        "\n",
        "fig, axs = plt.subplots(1, 3, figsize=(14, 5))\n",
        "\n",
        "# 2Q Depth\n",
        "bars = axs[0].bar(x, depths, color=colors)\n",
        "axs[0].set_ylabel(\"2Q Depth\", fontsize=11)\n",
        "axs[0].set_title(\"Two-Qubit Gate Depth\", fontsize=13)\n",
        "axs[0].set_ylim(0, max(depths) * 1.2)\n",
        "for bar, val in zip(bars, depths):\n",
        "    axs[0].text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + max(depths) * 0.02,\n",
        "        str(val),\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=11,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "for i in range(1, len(depths)):\n",
        "    pct = (depths[0] - depths[i]) / depths[0] * 100\n",
        "    if pct != 0:\n",
        "        axs[0].text(\n",
        "            bars[i].get_x() + bars[i].get_width() / 2,\n",
        "            bars[i].get_height() / 2,\n",
        "            f\"{pct:+.0f}%\",\n",
        "            ha=\"center\",\n",
        "            va=\"center\",\n",
        "            fontsize=10,\n",
        "            color=\"white\",\n",
        "            fontweight=\"bold\",\n",
        "        )\n",
        "\n",
        "# Size\n",
        "bars = axs[1].bar(x, sizes, color=colors)\n",
        "axs[1].set_ylabel(\"Gate Count\", fontsize=11)\n",
        "axs[1].set_title(\"Circuit Size\", fontsize=13)\n",
        "axs[1].set_ylim(0, max(sizes) * 1.2)\n",
        "for bar, val in zip(bars, sizes):\n",
        "    axs[1].text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + max(sizes) * 0.02,\n",
        "        str(val),\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=11,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "for i in range(1, len(sizes)):\n",
        "    pct = (sizes[0] - sizes[i]) / sizes[0] * 100\n",
        "    if abs(pct) > 0.1:\n",
        "        axs[1].text(\n",
        "            bars[i].get_x() + bars[i].get_width() / 2,\n",
        "            bars[i].get_height() / 2,\n",
        "            f\"{pct:+.0f}%\",\n",
        "            ha=\"center\",\n",
        "            va=\"center\",\n",
        "            fontsize=10,\n",
        "            color=\"white\",\n",
        "            fontweight=\"bold\",\n",
        "        )\n",
        "\n",
        "# Time\n",
        "bars = axs[2].bar(x, times, color=colors)\n",
        "axs[2].set_ylabel(\"Time (s)\", fontsize=11)\n",
        "axs[2].set_title(\"Transpilation Time\", fontsize=13)\n",
        "axs[2].set_ylim(0, max(times) * 1.3)\n",
        "for bar, val in zip(bars, times):\n",
        "    axs[2].text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + max(times) * 0.03,\n",
        "        f\"{val:.2f}s\",\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=11,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "\n",
        "for ax in axs:\n",
        "    ax.set_xticks(x)\n",
        "    ax.set_xticklabels(pm_names, fontsize=8, rotation=15)\n",
        "    ax.grid(axis=\"y\", linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "plt.suptitle(\n",
        "    \"Transpilation quality vs. configuration\",\n",
        "    fontsize=14,\n",
        "    fontweight=\"bold\",\n",
        "    y=1.02,\n",
        ")\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6a233965-1358-420b-8823-5f4e206090b4",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### 3단계: `Qiskit primitives` 명령어로 실행합니다\n",
        "\n",
        "우리는 실제 백엔드에서 도출한 노이즈 모델을 사용하여 Aer를 `EstimatorV2` 통해 트랜스파일된 각 회로를 **10회** 실행합니다. 시뮬레이션 결과는 실행마다 잡음이 발생하여 결과가 달라지기 때문에, 여러 번의 실행 결과를 평균화하면 더 신뢰할 수 있는 정확도 추정치를 얻을 수 있으며, 오차 막대를 통해 통계적 불확실성을 정량화할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "a91b9887-c8cf-48fd-a6fb-a5506d201f8d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Run 1/10 done\n",
            "Run 2/10 done\n",
            "Run 3/10 done\n",
            "Run 4/10 done\n",
            "Run 5/10 done\n",
            "Run 6/10 done\n",
            "Run 7/10 done\n",
            "Run 8/10 done\n",
            "Run 9/10 done\n",
            "Run 10/10 done\n",
            "pm_1 (4,20,20): mean fidelity = 0.9510 +/- 0.0094\n",
            "pm_2 (4,200,200): mean fidelity = 0.9513 +/- 0.0043\n",
            "pm_3 (8,200,200): mean fidelity = 0.9540 +/- 0.0065\n",
            "pm_star (default + StarPreRouting): mean fidelity = 0.9547 +/- 0.0072\n"
          ]
        }
      ],
      "source": [
        "# Create a noisy estimator from the real backend's noise model\n",
        "noisy_estimator = AerEstimator.from_backend(backend)\n",
        "\n",
        "num_runs = 10\n",
        "# sim_all_runs[name] = list of arrays, one per run\n",
        "sim_all_runs = {name: [] for name in results_sim}\n",
        "\n",
        "for run in range(num_runs):\n",
        "    for name, r in results_sim.items():\n",
        "        job = noisy_estimator.run([(r[\"tqc\"], r[\"ops\"])])\n",
        "        evs = list(job.result()[0].data.evs)\n",
        "        sim_all_runs[name].append(evs)\n",
        "    print(f\"Run {run + 1}/{num_runs} done\")\n",
        "\n",
        "# Compute mean and std across runs for each config\n",
        "sim_stats = {}\n",
        "for name in results_sim:\n",
        "    all_evs = np.array(sim_all_runs[name])  # shape (num_runs, num_operators)\n",
        "    sim_stats[name] = {\n",
        "        \"mean\": np.mean(all_evs, axis=0),\n",
        "        \"std\": np.std(all_evs, axis=0),\n",
        "        \"overall_mean\": np.mean(all_evs),\n",
        "        \"overall_std\": np.std(\n",
        "            np.mean(all_evs, axis=1)\n",
        "        ),  # std of per-run averages\n",
        "    }\n",
        "    print(\n",
        "        f\"{name}: mean fidelity = {sim_stats[name]['overall_mean']:.4f} +/- {sim_stats[name]['overall_std']:.4f}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89e6c70f-4da4-470f-b0fa-2e2521c82f6f",
      "metadata": {},
      "source": [
        "이 회로는 규모가 작기 때문에, 네 가지 구성 모두에서 충실도 값이 비교적 비슷한 수준을 보입니다. 회로가 충분히 짧기 때문에, 최적화가 가장 덜 된 버전이라 해도 하드웨어 노이즈로 인한 성능 저하가 크지 않습니다. 평균 충실도는 대체로 2Q 의 깊이와 유사한 경향을 보입니다: `pm_3` 와 `pm_star`, 즉 가장 얕은 두 회로가 가장 높은 충실도를 보이며, 오차 범위 내에서 사실상 동등한 수준입니다. `pm_1``pm_2` 는 유용한 반례입니다. 이 모델의 2Q 깊이는 보다 낮지만, 평균 충실도 역시 약간 더 낮은 것으로 나타납니다. 이는 깊이와 충실도 간의 관계가 결정론적이기보다는 통계적 성격을 띤다는 점을 상기시켜 줍니다. 레이아웃에서 선택하는 구체적인 큐비트와 실행 시 해당 큐비트의 보정 역시 중요합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e601648b-8f5f-4111-ba35-f64af63da909",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### 4단계: 후처리 수행 및 원하는 클래식 형식으로 결과 반환\n",
        "\n",
        "다음으로, 얽힘 상관관계 $\\langle Z_0 Z_i \\rangle$ 를 큐비트 거리의 함수로 그래프에 표시하고, 단일 충실도 지표로서의 **평균 상관관계** 도 함께 표시하십시오. 이상적인(잡음이 없는) 경우라면, 모든 상관관계는 1이 될 것입니다. 현실적인 잡음이 존재하는 상황에서, 게이트가 하나 추가될 때마다 오류가 발생하고 시간 단계가 하나 추가될 때마다 비결합이 일어나므로, 깊이가 더 얕고 게이트(특히 2-큐비트 게이트) 수가 적은 트랜스파일된 회로가 양자 얽힘을 더 잘 보존할 수 있다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "a6dac5ed-a963-458a-ada1-89c915f036e0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/a6dac5ed-a963-458a-ada1-89c915f036e0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "data_sim = list(range(1, len(operators_sim) + 1))\n",
        "markers = [\"o\", \"s\", \"^\", \"*\"]\n",
        "colors_line = [\"#404080\", \"#2a9d8f\", \"#a8d05e\", \"#e29bdd\"]\n",
        "\n",
        "fig, (ax1, ax2) = plt.subplots(\n",
        "    1, 2, figsize=(14, 5), gridspec_kw={\"width_ratios\": [2.5, 1]}\n",
        ")\n",
        "\n",
        "# Left: correlations vs distance with error bars (mean +/- 1 std)\n",
        "for (name, stats), marker, color in zip(\n",
        "    sim_stats.items(), markers, colors_line\n",
        "):\n",
        "    ax1.errorbar(\n",
        "        data_sim,\n",
        "        stats[\"mean\"],\n",
        "        yerr=stats[\"std\"],\n",
        "        marker=marker,\n",
        "        label=name,\n",
        "        color=color,\n",
        "        linewidth=2,\n",
        "        capsize=3,\n",
        "        capthick=1,\n",
        "        elinewidth=1,\n",
        "    )\n",
        "\n",
        "ax1.set_xlabel(\"Distance between qubits $i$\", fontsize=11)\n",
        "ax1.set_ylabel(r\"$\\langle Z_0 Z_i \\rangle$\", fontsize=11)\n",
        "ax1.set_title(\n",
        "    \"Entanglement correlations vs. qubit distance (avg. of 10 runs)\",\n",
        "    fontsize=12,\n",
        ")\n",
        "ax1.legend(fontsize=9)\n",
        "ax1.grid(alpha=0.3)\n",
        "\n",
        "# Right: mean correlation bar chart with error bars\n",
        "names = list(sim_stats.keys())\n",
        "means = [sim_stats[n][\"overall_mean\"] for n in names]\n",
        "stds = [sim_stats[n][\"overall_std\"] for n in names]\n",
        "x_bar = np.arange(len(names))\n",
        "bars = ax2.bar(\n",
        "    x_bar, means, yerr=stds, color=colors_line, capsize=5, ecolor=\"gray\"\n",
        ")\n",
        "ax2.set_ylabel(r\"Mean $\\langle Z_0 Z_i \\rangle$\", fontsize=11)\n",
        "ax2.set_title(\"Average fidelity\", fontsize=13, pad=12)\n",
        "y_range = max(means) - min(means) if max(means) != min(means) else 0.01\n",
        "# Top of ylim accounts for the bar height + std error bar + headroom for the value label\n",
        "y_top = max(m + s for m, s in zip(means, stds)) + y_range * 1.5\n",
        "ax2.set_ylim(min(means) - y_range * 0.8, y_top)\n",
        "for bar, val, std in zip(bars, means, stds):\n",
        "    ax2.text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + std + y_range * 0.15,\n",
        "        f\"{val:.4f}\",\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=10,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "# Annotate % change vs pm_1\n",
        "baseline_mean = means[0]\n",
        "for i in range(1, len(means)):\n",
        "    pct = (means[i] - baseline_mean) / baseline_mean * 100\n",
        "    if abs(pct) > 0.01:\n",
        "        mid_y = (means[i] + ax2.get_ylim()[0]) / 2\n",
        "        ax2.text(\n",
        "            bars[i].get_x() + bars[i].get_width() / 2,\n",
        "            mid_y,\n",
        "            f\"{pct:+.1f}%\",\n",
        "            ha=\"center\",\n",
        "            va=\"center\",\n",
        "            fontsize=10,\n",
        "            color=\"white\",\n",
        "            fontweight=\"bold\",\n",
        "        )\n",
        "ax2.set_xticks(x_bar)\n",
        "ax2.set_xticklabels(names, fontsize=8, rotation=15)\n",
        "ax2.grid(axis=\"y\", linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "fig.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "05235ab1-b473-4267-a34c-5f56e728550b",
      "metadata": {},
      "source": [
        "결과에 따르면, 트랜스필레이션 품질과 실행 정확도 사이에는 명확한 연관성이 있는 것으로 나타났으며, 몇 가지 유의할 점이 있습니다:\n",
        "\n",
        "* **`pm_1` (기본값)** : 기준선. 단 20회의 시뮬레이션과 4회의 반복 과정만 거친 SABRE는 최적화 여지가 제한적이어서, SABRE만으로 구현된 회로 중 가장 깊은 구조를 갖게 되었습니다.\n",
        "* **`pm_2` (추가 실험)** : 후보 레이아웃을 10배 더 많이 탐색하면 약간 더 얕은 레이아웃이 발견되지만, 이 규모에서는 깊이 증가폭이 작기 때문에 평균 충실도는 대체로 변함이 없으며(심지어 노이즈 범위 내에서 기준치보다 낮아지기도 한다).\n",
        "* **`pm_3` (더 많은 시도 + 더 많은 반복)** : 값을 `max_iterations` 8로 두 배로 늘리면 SABRE가 더 많은 정제 주기를 수행하게 되어, 비교 대상 중 가장 얕은 SABRE 전용 회로와 가장 높은 평균 충실도를 얻을 수 있다.\n",
        "* **`pm_star` (기본값 + StarPreRouting )** : 기본 프리셋의 초기화 단계에 를 추가합니다 `StarPreRouting` . `pm_3`구조 인식 재작성 과정은 별 모양 구조를 선형 사슬로 축소하며, 트랜스파일러의 나머지 단계에서는 이를 장치의 선형 경로에 매핑함으로써 전체적으로 가장 얕은 회로를 생성합니다(보다 약간 더 우수하며), 오차 범위 내에서 와 `pm_3` 동일한 충실도를 보입니다. 이 작업은 기본 설정과 동일한 트랜스파일링 시간을 소요하는데, 이는 SABRE의 확률적 탐색에 비해 재작성 작업이 사실상 추가 비용이 들지 않기 때문이다.\n",
        "\n",
        "를 늘린다고 `max_iterations` 해서 항상 긍정적인 효과가 나타나는 것은 아니라는 점에 유의하십시오. 이 경우에는 상당한 도움이 되었지만, 다른 회로나 백엔드의 경우 추가적인 반복 과정을 거더라도 더 이상의 성능 향상을 기대하기 어려울 수 있으며, 오히려 국부적 최소점에 대한 과도한 최적화로 인해 성능이 약간 저하될 수도 있습니다. 일반적으로, 시도 횟수가 많을수록 더 나은 레이아웃을 찾을 확률이 높아지므로, 시간 예산이 허용하는 한 와 `swap_trials` 를 최대한 늘려야 `layout_trials` 합니다. 증가시키는 `max_iterations` 것은 시도해 볼 만한 가치가 있지만, 구체적인 사용 사례에 맞춰 검증해야 합니다. 와 같은 `StarPreRouting` 특수한 패스는 취지는 비슷하지만 회로에 더 의존적입니다. 즉, 해당 회로에 대상 구조가 실제로 포함되어 있을 때만 도움이 됩니다. 적용 가능한 경우에는 이득이 크지만, 그렇지 않은 경우에는 이득이 없으나, 시도해 보는 데는 사실상 비용이 들지 않습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c2b60e42-4aa2-4dc1-8a11-022072e79776",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## 대규모 하드웨어 예시\n",
        "\n",
        "SABRE는 시험 횟수를 조정하는 것 외에도 **라우팅 휴리스틱** 을 사용자 정의할 수 있도록 지원합니다. SABRE는 세 가지 휴리스틱을 제공합니다:\n",
        "\n",
        "* **`basic`**: 다음 게이트까지의 즉각적인 거리를 최소화하는 스왑을 선택하는 간단한 탐욕적 접근법이다.\n",
        "* **`decay`** (기본값): 최근 활동 내역을 바탕으로 큐비트의 가중치를 동적으로 조정하여, 동일한 큐비트에 대한 반복적인 교환을 방지합니다.\n",
        "* **`lookahead`**: 앞으로 다가올 게이트를 미리 살펴봄으로써 향후 경로 비용을 평가하고, 더 나은 스왑 순서를 찾아낼 수 있습니다.\n",
        "\n",
        "사용자 정의 휴리스틱을 사용하려면 패스를 `SabreSwap` 생성하고 매개변수를 `routing_pass` 통해 해당 패스를 에 `SabreLayout` 연결하십시오.\n",
        "\n",
        "`SabreLayout`비교 대상에 네 번째 패스 관리자가 추가되었습니다. `pm_star_hw` 이 관리자는 기본값/설정을`SabreSwap` 유지하되 초기화 단계에 를 추가합니다 `StarPreRouting` . 이 규모(100 큐비트)에서는 SABRE 탐색이 더 어려워지며, 스타 구조에서 선형 체인으로 재구성하는 것이 명백한 이점을 가져다줍니다. 헤론 프로세서는 결과 회로를 수용할 수 있을 만큼 충분히 긴 선형 경로를 갖추고 있기 때문입니다.\n",
        "\n",
        "여기서는 세 가지 SABRE 휴리스틱을 모두 비교하고, `StarPreRouting` 100 큐비트 GHZ 회로에서 대규모 시뮬레이션을 수행합니다. SABRE 구성을 위해 서로 다른 시드 값을 사용하여 여러 번의 레이아웃 시뮬레이션을 수행하고, 각 시뮬레이션에서 가장 우수한 트랜스파일된 회로를 선정한 뒤, 이를 결과와 `StarPreRouting` 함께 실제 하드웨어에 제출합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d755fafa-bbbc-4191-9ad9-a77f2af1bedc",
      "metadata": {},
      "source": [
        "<span id=\"steps-1-4-compressed-into-a-single-code-block\" />\n",
        "\n",
        "### 1\\~4단계를 하나의 코드 블록으로 압축함\n",
        "\n",
        "여기서는 전체 워크플로우를 더 큰 규모로 통합하여 보여줍니다. `SabreLayout`를 `routing_pass` 의 로 사용할 `SabreSwap` 경우, 호출당 단 하나의 레이아웃 시도가 수행되므로, 다음 코드 셀은 시드 값을 순회하며 레이아웃 공간을 탐색합니다.\n",
        "\n",
        "우리는 (위의) 소규모 2단계에서 정의된 것과 동일한 `wrap_sabre` 헬퍼를 사용하며, 인덱스 \\[1] 에 있는 `routing` 스테이지도 이기 때문에 이에 `ConditionalController([BarrierBeforeFinalMeasurements, routing_pass], ...)` 상응하는 `wrap_routing` 헬퍼를 추가합니다. 이를 그대로 대체하면 마찬가지로 보호 장벽과 게이팅이 `_swap_condition` 사라지게 될 것입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "4feb0fcf-d305-4741-82a7-b0fe0f0894fc",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "\n",
        "num_qubits = 100\n",
        "\n",
        "# Create star-topology GHZ circuit\n",
        "qc = QuantumCircuit(num_qubits)\n",
        "qc.h(0)\n",
        "for i in range(1, num_qubits):\n",
        "    qc.cx(0, i)\n",
        "qc.measure_all()\n",
        "\n",
        "# ZZ operators\n",
        "operator_strings = [\n",
        "    \"Z\" + \"I\" * i + \"Z\" + \"I\" * (num_qubits - 2 - i)\n",
        "    for i in range(num_qubits - 1)\n",
        "]\n",
        "operators = [SparsePauliOp(op) for op in operator_strings]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "43ec98c6-f4c6-4584-8ba0-edfb4e99f04a",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "basic:\n",
            "  2Q depth: min: 524, mean: 570.5, std: 39.9\n",
            "  size    : min: 3819, mean: 4227.1, std: 360.6\n",
            "  best seed: 51 (2Q depth=524, size=3852)\n",
            "decay:\n",
            "  2Q depth: min: 387, mean: 436.4, std: 41.7\n",
            "  size    : min: 2687, mean: 3183.1, std: 459.3\n",
            "  best seed: 45 (2Q depth=387, size=2786)\n",
            "lookahead:\n",
            "  2Q depth: min: 364, mean: 424.6, std: 36.5\n",
            "  size    : min: 2335, mean: 3014.6, std: 388.1\n",
            "  best seed: 51 (2Q depth=364, size=2485)\n",
            "StarPreRouting:\n",
            "  2Q depth: min: 196, mean: 196.0, std: 0.0\n",
            "  size    : min: 1151, mean: 1151.0, std: 0.0\n",
            "  best seed: 42 (2Q depth=196, size=1151)\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 2-------------------------\n",
        "\n",
        "num_seeds = 10\n",
        "seed_list = [seed + i for i in range(num_seeds)]\n",
        "swap_trials = 200\n",
        "\n",
        "\n",
        "# The default routing[1] is a ConditionalController([barrier, routing_pass],\n",
        "# condition=_swap_condition); we re-wrap so the new routing pass keeps the\n",
        "# protective barrier and is skipped when routing isn't needed (matches the preset).\n",
        "def _swap_condition(property_set):\n",
        "    return not property_set[\"routing_not_needed\"]\n",
        "\n",
        "\n",
        "def wrap_routing(routing_pass):\n",
        "    return ConditionalController(\n",
        "        [\n",
        "            BarrierBeforeFinalMeasurements(\n",
        "                \"qiskit.transpiler.internal.routing.protection.barrier\"\n",
        "            ),\n",
        "            routing_pass,\n",
        "        ],\n",
        "        condition=_swap_condition,\n",
        "    )\n",
        "\n",
        "\n",
        "heuristic_results = {}\n",
        "\n",
        "# Three SABRE heuristics, swept over seeds\n",
        "for heuristic in [\"basic\", \"decay\", \"lookahead\"]:\n",
        "    trials = []\n",
        "    for s in seed_list:\n",
        "        sr = SabreSwap(\n",
        "            coupling_map=cmap, heuristic=heuristic, trials=swap_trials, seed=s\n",
        "        )\n",
        "        sl = SabreLayout(coupling_map=cmap, routing_pass=sr, seed=s)\n",
        "        pm = generate_preset_pass_manager(\n",
        "            optimization_level=3, backend=backend, seed_transpiler=s\n",
        "        )\n",
        "        # Re-wrap each custom pass in its original ConditionalController + barrier\n",
        "        # (wrap_sabre is defined in the small-scale Step 2 cell above).\n",
        "        pm.layout.replace(index=2, passes=wrap_sabre(sl))\n",
        "        pm.routing.replace(index=1, passes=wrap_routing(sr))\n",
        "\n",
        "        t0 = time.time()\n",
        "        tqc = pm.run(qc)\n",
        "        elapsed = time.time() - t0\n",
        "        depth = tqc.depth(lambda x: x.operation.num_qubits == 2)\n",
        "        size = tqc.size()\n",
        "        trials.append(\n",
        "            {\n",
        "                \"tqc\": tqc,\n",
        "                \"depth\": depth,\n",
        "                \"size\": size,\n",
        "                \"time\": elapsed,\n",
        "                \"seed\": s,\n",
        "            }\n",
        "        )\n",
        "\n",
        "    heuristic_results[heuristic] = trials\n",
        "\n",
        "# Default preset + StarPreRouting in init, also swept over seeds for a fair comparison\n",
        "star_trials = []\n",
        "for s in seed_list:\n",
        "    pm_star_hw = generate_preset_pass_manager(\n",
        "        optimization_level=3, backend=backend, seed_transpiler=s\n",
        "    )\n",
        "    pm_star_hw.init += StarPreRouting()\n",
        "\n",
        "    t0 = time.time()\n",
        "    tqc = pm_star_hw.run(qc)\n",
        "    elapsed = time.time() - t0\n",
        "    depth = tqc.depth(lambda x: x.operation.num_qubits == 2)\n",
        "    size = tqc.size()\n",
        "    star_trials.append(\n",
        "        {\n",
        "            \"tqc\": tqc,\n",
        "            \"depth\": depth,\n",
        "            \"size\": size,\n",
        "            \"time\": elapsed,\n",
        "            \"seed\": s,\n",
        "        }\n",
        "    )\n",
        "heuristic_results[\"StarPreRouting\"] = star_trials\n",
        "\n",
        "# Print summary for each entry\n",
        "for label in [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]:\n",
        "    trials = heuristic_results[label]\n",
        "    depths = [t[\"depth\"] for t in trials]\n",
        "    sizes = [t[\"size\"] for t in trials]\n",
        "    best = min(trials, key=lambda t: t[\"depth\"])\n",
        "    print(f\"{label}:\")\n",
        "    print(\n",
        "        f\"  2Q depth: min: {min(depths)}, mean: {np.mean(depths):.1f}, std: {np.std(depths):.1f}\"\n",
        "    )\n",
        "    print(\n",
        "        f\"  size    : min: {min(sizes)}, mean: {np.mean(sizes):.1f}, std: {np.std(sizes):.1f}\"\n",
        "    )\n",
        "    print(\n",
        "        f\"  best seed: {best['seed']} (2Q depth={best['depth']}, size={best['size']})\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "eead9bd2-17e0-4f5b-80bc-eb9b30af052e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/eead9bd2-17e0-4f5b-80bc-eb9b30af052e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "basic: best 2Q depth=524, size=3852 (seed=51)\n",
            "decay: best 2Q depth=387, size=2786 (seed=45)\n",
            "lookahead: best 2Q depth=364, size=2485 (seed=51)\n",
            "StarPreRouting: best 2Q depth=196, size=1151 (seed=42)\n"
          ]
        }
      ],
      "source": [
        "hw_colors = {\n",
        "    \"basic\": \"#ff7f0e\",\n",
        "    \"decay\": \"#d62728\",\n",
        "    \"lookahead\": \"#1f77b4\",\n",
        "    \"StarPreRouting\": \"#2a9d8f\",\n",
        "}\n",
        "\n",
        "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))\n",
        "\n",
        "for label in [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]:\n",
        "    trials = heuristic_results[label]\n",
        "    depths = [t[\"depth\"] for t in trials]\n",
        "    sizes = [t[\"size\"] for t in trials]\n",
        "    seeds = [t[\"seed\"] for t in trials]\n",
        "    color = hw_colors[label]\n",
        "\n",
        "    ax1.scatter(\n",
        "        seeds,\n",
        "        depths,\n",
        "        label=label,\n",
        "        color=color,\n",
        "        alpha=0.8,\n",
        "        edgecolor=\"k\",\n",
        "        s=60,\n",
        "    )\n",
        "    ax1.axhline(np.mean(depths), color=color, linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "    ax2.scatter(\n",
        "        seeds,\n",
        "        sizes,\n",
        "        label=label,\n",
        "        color=color,\n",
        "        alpha=0.8,\n",
        "        edgecolor=\"k\",\n",
        "        s=60,\n",
        "    )\n",
        "    ax2.axhline(np.mean(sizes), color=color, linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "ax1.set_xlabel(\"Seed\", fontsize=11)\n",
        "ax1.set_ylabel(\"2Q Depth\", fontsize=11)\n",
        "ax1.set_title(\"Two-Qubit Gate Depth per Seed\", fontsize=13)\n",
        "ax1.legend(fontsize=10)\n",
        "ax1.grid(alpha=0.3)\n",
        "\n",
        "ax2.set_xlabel(\"Seed\", fontsize=11)\n",
        "ax2.set_ylabel(\"Gate Count\", fontsize=11)\n",
        "ax2.set_title(\"Circuit Size per Seed\", fontsize=13)\n",
        "ax2.legend(fontsize=10)\n",
        "ax2.grid(alpha=0.3)\n",
        "\n",
        "plt.suptitle(\n",
        "    \"Transpilation variability across seeds: SABRE heuristics vs. StarPreRouting\",\n",
        "    fontsize=14,\n",
        "    fontweight=\"bold\",\n",
        "    y=1.02,\n",
        ")\n",
        "plt.tight_layout()\n",
        "plt.show()\n",
        "\n",
        "# Summary comparison\n",
        "for label in [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]:\n",
        "    best = min(heuristic_results[label], key=lambda t: t[\"depth\"])\n",
        "    print(\n",
        "        f\"{label}: best 2Q depth={best['depth']}, size={best['size']} (seed={best['seed']})\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "5ad47245-41d0-4d90-ba94-dda4cd63705d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Best basic: 2Q depth=524, size=3852\n",
            "Best decay: 2Q depth=387, size=2786\n",
            "Best lookahead: 2Q depth=364, size=2485\n",
            "Best StarPreRouting: 2Q depth=196, size=1151\n",
            "basic job: d81q5tnoha1c73bknprg\n",
            "decay job: d81q5tugbeec73aktopg\n",
            "lookahead job: d81q5to0bvlc73d1epe0\n",
            "StarPreRouting job: d81q5u7tjchs73bn82hg\n",
            "basic job done\n",
            "decay job done\n",
            "lookahead job done\n",
            "StarPreRouting job done\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 3: Execute on hardware-------------------------\n",
        "\n",
        "best_circuits = {}\n",
        "for label in [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]:\n",
        "    best_circuits[label] = min(\n",
        "        heuristic_results[label], key=lambda t: t[\"depth\"]\n",
        "    )\n",
        "    b = best_circuits[label]\n",
        "    print(f\"Best {label}: 2Q depth={b['depth']}, size={b['size']}\")\n",
        "\n",
        "options = EstimatorOptions()\n",
        "options.resilience_level = 2\n",
        "options.dynamical_decoupling.enable = True\n",
        "options.dynamical_decoupling.sequence_type = \"XY4\"\n",
        "estimator = Estimator(backend, options=options)\n",
        "\n",
        "hw_jobs = {}\n",
        "hw_ops = {}\n",
        "for label, best in best_circuits.items():\n",
        "    hw_ops[label] = [op.apply_layout(best[\"tqc\"].layout) for op in operators]\n",
        "    hw_jobs[label] = estimator.run([(best[\"tqc\"], hw_ops[label])])\n",
        "    print(f\"{label} job: {hw_jobs[label].job_id()}\")\n",
        "estimator.options.environment.job_tags = [\"TUT_TOWS\"]\n",
        "\n",
        "hw_results = {}\n",
        "for label, job in hw_jobs.items():\n",
        "    hw_results[label] = job.result()[0]\n",
        "    print(f\"{label} job done\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "0280b0b9-6320-43e5-8396-f82f9e718319",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transpilation-optimizations-with-sabre/extracted-outputs/0280b0b9-6320-43e5-8396-f82f9e718319-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "Mean fidelity:\n",
            "  basic: 0.0344\n",
            "  decay: 0.1298\n",
            "  lookahead: 0.1857\n",
            "  StarPreRouting: 0.3295\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 4: Post-process-------------------------\n",
        "\n",
        "data = list(range(1, len(operators) + 1))\n",
        "hw_markers = {\n",
        "    \"basic\": \"D\",\n",
        "    \"decay\": \"o\",\n",
        "    \"lookahead\": \"s\",\n",
        "    \"StarPreRouting\": \"*\",\n",
        "}\n",
        "hw_labels = [\"basic\", \"decay\", \"lookahead\", \"StarPreRouting\"]\n",
        "\n",
        "fig, (ax1, ax2) = plt.subplots(\n",
        "    1, 2, figsize=(14, 5), gridspec_kw={\"width_ratios\": [2.5, 1]}\n",
        ")\n",
        "\n",
        "# Left: correlations vs distance\n",
        "for label in hw_labels:\n",
        "    evs = list(hw_results[label].data.evs)\n",
        "    b = best_circuits[label]\n",
        "    ax1.plot(\n",
        "        data,\n",
        "        evs,\n",
        "        marker=hw_markers[label],\n",
        "        color=hw_colors[label],\n",
        "        linewidth=2,\n",
        "        label=f\"{label} (2Q depth={b['depth']}, size={b['size']})\",\n",
        "        markersize=5 if label == \"StarPreRouting\" else 4,\n",
        "    )\n",
        "\n",
        "ax1.set_xlabel(\"Distance between qubits $i$\", fontsize=11)\n",
        "ax1.set_ylabel(r\"$\\langle Z_0 Z_i \\rangle$\", fontsize=11)\n",
        "ax1.set_title(\n",
        "    \"Entanglement correlations vs. qubit distance (hardware)\", fontsize=12\n",
        ")\n",
        "ax1.legend(fontsize=9)\n",
        "ax1.grid(alpha=0.3)\n",
        "\n",
        "# Right: mean fidelity bar chart\n",
        "hw_means = [np.mean(list(hw_results[label].data.evs)) for label in hw_labels]\n",
        "hw_bar_colors = [hw_colors[label] for label in hw_labels]\n",
        "x_bar = np.arange(len(hw_labels))\n",
        "bars = ax2.bar(x_bar, hw_means, color=hw_bar_colors)\n",
        "ax2.set_ylabel(r\"Mean $\\langle Z_0 Z_i \\rangle$\", fontsize=11)\n",
        "ax2.set_title(\"Average fidelity\", fontsize=13)\n",
        "y_range = (\n",
        "    max(hw_means) - min(hw_means) if max(hw_means) != min(hw_means) else 0.01\n",
        ")\n",
        "ax2.set_ylim(min(hw_means) - y_range * 0.2, max(hw_means) + y_range * 0.15)\n",
        "for bar, val in zip(bars, hw_means):\n",
        "    ax2.text(\n",
        "        bar.get_x() + bar.get_width() / 2,\n",
        "        bar.get_height() + y_range * 0.05,\n",
        "        f\"{val:.4f}\",\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\",\n",
        "        fontsize=11,\n",
        "        fontweight=\"bold\",\n",
        "    )\n",
        "ax2.set_xticks(x_bar)\n",
        "ax2.set_xticklabels(hw_labels, fontsize=9, rotation=15)\n",
        "ax2.grid(axis=\"y\", linestyle=\"--\", alpha=0.5)\n",
        "\n",
        "fig.tight_layout()\n",
        "plt.show()\n",
        "\n",
        "print(\"\\nMean fidelity:\")\n",
        "for label, m in zip(hw_labels, hw_means):\n",
        "    print(f\"  {label}: {m:.4f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "151e5fe9-872a-4b89-92f9-85bb883de17b",
      "metadata": {},
      "source": [
        "<span id=\"analysis\" />\n",
        "\n",
        "### 분석\n",
        "\n",
        "산포도를 보면 세 가지 SABRE 휴리스틱 모두에서 시드 간에 상당한 변동성이 나타나는데, 이는 단일 트랜스필레이션에만 의존하기보다는 여러 번의 레이아웃 시도를 수행하는 것이 중요함을 강조해 준다. 시드에 따라 이 `StarPreRouting` 선은 본질적으로 평평한 양상을 보입니다. 이는 주어진 구조 하에서 별 모양 구조를 선형 체인으로 재구성하는 과정이 결정론적이기 때문이며, 이후 진행되는 SABRE 라우팅은 선형 체인 상에서 선택의 여지가 거의 없기 때문에, 시드가 최종 깊이 또는 크기에 미치는 영향은 거의 없습니다.\n",
        "\n",
        "전사 결과에서 볼 때, 및 `lookahead` 휴리스틱 `decay` 모두 보다 일관되게 훨씬 더 우수한 `basic` 성능을 보였다. 이 `basic` 휴리스틱은 속도는 빠르지만, 단순한 탐욕적 전략을 사용하기 때문에 회로 구조가 상당히 복잡해지는 경우가 많습니다. 이 스타 토폴로지 GHZ 회로는, `lookahead` SABRE 휴리스틱 중 가장 낮은 2Q 깊이와 게이트 수를 산출하는 경향이 있는데, 이는 이 회로의 전방지향적 비용 함수가 장거리 연결 패턴을 가진 회로에 매우 적합하기 때문이다. `StarPreRouting`, 그러나 이 방법은 다른 세 가지 방법보다 훨씬 뛰어난 성능을 보인다. 라우팅 전에 스타 구조를 선형 체인으로 재구성함으로써 검색 문제를 완전히 우회하고, 나머지 트랜스파일러가 최소한의 추가 SWAP만으로 선형 경로에 매핑할 수 있는 회로를 생성한다.\n",
        "\n",
        "이러한 장점은 하드웨어의 정밀도로도 그대로 이어집니다. 2Q 의 깊이와 게이트 수가 적다고 해서 항상 그와 비례하여 정밀도가 높아지는 것은 아닙니다(레이아웃에서 사용하는 구체적인 물리적 큐비트와 실행 시의 보정 역시 중요하기 때문이죠). 하지만 SABRE와 `StarPreRouting` 여기 사이의 깊이 차이가 이 정도로 클 경우, 회로에서 발생하는 디코히어런스와 2-큐비트 오류 발생 건수가 훨씬 적기 때문에 구조 인식 접근법이 압도적으로 우위를 차지합니다. 정확도 막대 그래프를 보면 `StarPreRouting` , 는 최고의 SABRE 휴리스틱보다 훨씬 앞선 성능을 보이는 반면 `basic` , 는 회로 구조가 훨씬 복잡하여 오류가 가장 많이 누적되기 때문에 나머지 모델들보다 훨씬 낮은 수준에 머물고 있다.\n",
        "\n",
        "**주요 내용:**\n",
        "\n",
        "* SABRE 휴리스틱 중, `decay` 와 `lookahead` 는 비사소한 회로에 대해 보다 `basic` 훨씬 더 우수한 성능을 보인다. 프로덕션 워크로드에는 이 두 가지 중 하나를 선택하십시오.\n",
        "* 가장 적합한 SABRE 휴리스틱은 회로와 하드웨어에 따라 달라집니다. 여러 시드를 사용하여 여러 가지 휴리스틱을 테스트하는 것이 가장 신뢰할 수 있는 전략입니다.\n",
        "* 더 많은 레이아웃을 탐색하고 싶다면, 작업을 원격 노드로 분산시키는 대신 (그리고 `layout_trials` 사용자 정의 라우팅 패스를 핀하지 않을 때는) 값을 늘리세요 `swap_trials` . SABRE 패스는 이미 로컬 스레드 간에 트라이얼을 병렬화하고 있으며, 트라이얼당 작업량이 매우 적기 때문에 일반적으로 분산 처리로 인한 오버헤드가 속도 향상 효과를 상쇄합니다.\n",
        "* 회로에 알려진 특수한 구조가 있는 경우, SABRE 실행 `StarPreRouting` 전에 구조 인식 패스를 적용하면 아무리 SABRE를 튜닝해도 따라올 수 없는 한 차원 높은 성능 향상을 얻을 수 있습니다. 이는 SABRE를 대체하는 것이 아닙니다. `StarPreRouting` 회로에 실제로 스타형 하위 회로가 포함되어 있고 백엔드의 선형 경로가 충분히 길 때만 도움이 됩니다. 회로 모양을 파악했다면, 패스 라이브러리에서 일치하는 항목이 있는지 확인해 볼 가치가 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "65c2cebe-50b9-4304-9123-bf4cea7ecff6",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 다음 단계\n",
        "\n",
        "이 글이 흥미로웠다면, 다음 자료도 참고해 보시기 바랍니다:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"권장사항\">\n",
        "  * [`SabreLayout` API 참조](/docs/api/qiskit/qiskit.transpiler.passes.SabreLayout) : 전체 매개변수 설명서\n",
        "  * [SABRE 논문](https://arxiv.org/abs/1809.02573) : 레이아웃 및 라우팅을 위한 최초의 SABRE 알고리즘\n",
        "  * [LightSABRE 논문](https://arxiv.org/abs/2409.08368) : Qiskit의 현재 SABRE 구현을 뒷받침하는 알고리즘 개선 사항\n",
        "  * [사용자 정의 트랜스파일러 패스 작성](/docs/guides/custom-transpiler-pass) : 나만의 트랜스파일링 로직 구축하기\n",
        "  * [트랜스파일러 플러그인](/docs/guides/transpiler-plugins) : 타사 패스를 통해 Qiskit의 트랜스파일링 파이프라인 확장하기\n",
        "  * [DAG 표현](/docs/guides/DAG-representation) : 트랜스파일러가 내부적으로 사용하는 유향 비순환 그래프(DAG) 이해하기\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42d1c053-1683-4c32-be1b-a36602207f74",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## 튜토리얼 설문조사\n",
        "\n",
        "이 튜토리얼에 대한 피드백을 제공하려면 간단한 설문조사에 참여해 주세요. 여러분의 인사이트는 콘텐츠 제공과 사용자 경험을 개선하는 데 도움이 됩니다.\n",
        "\n",
        "[설문조사 링크](https://your.feedback.ibm.com/jfe/form/SV_d9YWUSQIAvU9HXE)\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": 60
  },
  "nbformat": 4,
  "nbformat_minor": 4
}