{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"역전파 과정에서 파울리 항을 잘라내기\"\n",
        "description: \"최신 버전의 연산자 역전파(OBP)에서 역전파 과정 중 파울리 항을 잘라내기\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "11cf5076-2777-499c-be50-531e305bf011",
      "metadata": {},
      "source": [
        "<span id=\"truncate-pauli-terms-during-backpropagation\" />\n",
        "\n",
        "# 역전파 과정에서 파울리 항을 잘라내기\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c1453472-3506-4008-b432-f8ef9ccbf3ee",
      "metadata": {},
      "source": [
        "이 가이드에서는 [qiskit\\_addon\\_obp.utils.truncating](/docs/api/qiskit-addon-obp/utils-truncating#module-qiskit_addon_obp.utils.truncating) 모듈에서 제공하는 파울리 항 절단 메커니즘을 구성하는 방법을 설명합니다.\n",
        "\n",
        "연산자 역전파(OBP)를 활용하면, 관측량의 복잡성이 증가하는 대가로 양자 회로의 깊이를 줄일 수 있다. OBP에서 의미 있는 결과를 얻기 위해서는, 일반적으로 관측 가능한 변수의 항이 지나치게 커지는 것을 방지하기 위해 해당 항들을 잘라내야 합니다. 연산자가 지나치게 커지는 것을 방지하면서도 회로 내로 더 깊이 역전파가 이루어지도록 하는 한 가지 방법은, 계수가 작은 항들을 연산자에 더하는 대신 잘라내는 것입니다. 항을 잘라내면 실행해야 할 양자 회로의 수가 줄어들 수 있지만, 이렇게 할 경우 잘라낸 항의 계수 크기에 비례하는 오류가 최종 기대값 계산에 발생하게 된다.\n",
        "\n",
        "[백프로파게이션](/docs/api/qiskit-addon-obp/qiskit-addon-obp#backpropagate) 메서드는 선택적 매개변수인 [TruncationErrorBudget을](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget) 받으며, 이 매개변수는 각 슬라이스의 백프로파게이션이 성공적으로 완료된 후 각 관측량에 대해 가중치가 낮은 파울리 항의 절삭을 구성합니다.\n",
        "잘리는 항의 개수는 사용자가 지정한 다양한 구성 매개변수에 따라 달라집니다.\n",
        "현재로서는 [‘truncate\\_binary\\_search](/docs/api/qiskit-addon-obp/utils-truncating#truncate_binary_search) ’ 메서드라는 단 하나의 잘라내기 전략만 사용할 수 있습니다.\n",
        "주어진 관측량과 *예산* 에 따라, 이 프로그램은 해당 관측량 내의 파울리 항과 계수에 대해 이진 탐색을 수행하여, 잘린 계수들의 합이 최대이면서 예산보다 작은 최적의 임계값을 찾습니다.\n",
        "\n",
        "**참고** : 기본적으로 절단 오차를 평가하고 상한을 설정하는 데 L1 노름이 사용되지만, 이 설정을 `p_norm` 활성화하면 사용할 Lp-노름을 지정할 수 있습니다.\n",
        "해당 설정의 사용 방법에 대한 자세한 내용은 [‘파울리 항 절단을 위한 서로 다른 Lp-노름](/docs/addons/qiskit-addon-obp/guides/bound-error-using-p-norm) 사용 가이드를 참조하십시오.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaf5f046-6d5e-4b6f-add7-b60604edef30",
      "metadata": {},
      "source": [
        "다음 예제는 함께 제공되는 [setup\\_budget](/docs/api/qiskit-addon-obp/utils-truncating#setup_budget) 함수를 사용하여 [TruncationErrorBudget](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget) 를 구성하는 다양한 방법을 보여줍니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c83c0741-4a9e-4f89-92a9-4756ef133130",
      "metadata": {},
      "source": [
        "<span id=\"construct-an-example-circuit\" />\n",
        "\n",
        "## 예제 회로를 구성해 보세요\n",
        "\n",
        "이 가이드에서는 다음 회로 슬라이스를 사용합니다:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "297fc3b0-d3b4-432d-96cd-7e175b7e6a52",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/297fc3b0-d3b4-432d-96cd-7e175b7e6a52-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import rustworkx.generators\n",
        "from qiskit.synthesis import LieTrotter\n",
        "from qiskit_addon_utils.problem_generators import (\n",
        "    PauliOrderStrategy,\n",
        "    generate_time_evolution_circuit,\n",
        "    generate_xyz_hamiltonian,\n",
        ")\n",
        "from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types\n",
        "\n",
        "# Generate a linear chain of 10 qubits\n",
        "linear_chain = rustworkx.generators.path_graph(10)\n",
        "\n",
        "# Use an arbitrary XY model\n",
        "hamiltonian = generate_xyz_hamiltonian(\n",
        "    linear_chain,\n",
        "    coupling_constants=(0.05, 0.02, 0.0),\n",
        "    ext_magnetic_field=(0.02, 0.08, 0.0),\n",
        "    pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,\n",
        ")\n",
        "# Evolve for some time\n",
        "circuit = generate_time_evolution_circuit(\n",
        "    hamiltonian, synthesis=LieTrotter(reps=3), time=2.0\n",
        ")\n",
        "# slice the circuit by gate type\n",
        "slices = slice_by_gate_types(circuit)\n",
        "\n",
        "# for visualization purposes, recombine the slices with barriers between them and draw the resulting circuit\n",
        "combine_slices(slices, include_barriers=True).draw(\"mpl\", fold=50, scale=0.6)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89ce4fa9-06ba-4009-8594-d1b85f5e719f",
      "metadata": {},
      "source": [
        "여기서는 하나의 간단한 관측량을 살펴보겠습니다:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "b168d34c-f147-4524-9af9-264f56440a55",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "obs = SparsePauliOp(\"IIIIIZIIII\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "723dcf4e-1dab-4c26-a8e4-cd30b1564189",
      "metadata": {},
      "source": [
        "<span id=\"the-simplest-case-a-fixed-truncation-budget-for-each-slice\" />\n",
        "\n",
        "## 가장 간단한 경우: 각 슬라이스에 대해 고정된 잘라내기 할당량\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5e04e5c4-1e6b-4720-9563-becbb9edc4d7",
      "metadata": {},
      "source": [
        "파울리 항의 생략에 할당되는 예산은 역전파의 각 단계마다 달라질 수 있다.\n",
        "이것이 어떻게 작동하는지 이해하기 위해, 먼저 사용자가 지정한 고정된 절단 예산이라는 가장 간단한 경우를 살펴보겠습니다.\n",
        "\n",
        "잘림 예산을 지정하는 가장 간단한 방법은 인수를 `max_error_per_slice` 사용하는 것입니다.\n",
        "사실, [‘연산자 역전파를 이용한 회로 깊이 줄이기’](/docs/tutorials/operator-back-propagation) 튜토리얼에서 바로 이러한 방식이 사용되고 있습니다. 를 로 `max_error_per_slice` 설정하면 각 슬라이스에 해당 값과 동일한 예산이 할당됩니다 `float` .\n",
        "아래 예시에서, 이 값을 로 `0.001` 설정하면 18개의 슬라이스 모두에 대해 역전파가 수행될 경우 암시적 절단 오차가 최대 로 제한됩니다 `0.018`.\n",
        "\n",
        "<div class=\"alert alert-info\">\n",
        "  슬라이스에 대해 역전파를 수행하고 계수가 작은 항들을 잘라낸 후 남은 오차 예산은 항상 다음 슬라이스의 오차 예산에 더해진다는 점에 유의하십시오.\n",
        "</div>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "5f20a9b9-c3c4-43c4-9f13-fdf451958081",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "from qiskit_addon_obp.utils.truncating import setup_budget\n",
        "\n",
        "truncation_error_budget = setup_budget(max_error_per_slice=0.001)\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "c3b8b9ef-7d5c-43dd-8cb9-90756d4753b7",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 11 circuit slices.\n",
            "New observable contains 29 terms and 10 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "from qiskit_addon_obp import backpropagate\n",
        "from qiskit_addon_obp.utils.simplify import OperatorBudget\n",
        "\n",
        "op_budget = OperatorBudget(max_qwc_groups=10)\n",
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4a772af6-edc6-4e53-81c7-53f3b72bf58a",
      "metadata": {},
      "source": [
        "다음으로 [OBPMetadata](/docs/api/qiskit-addon-obp/utils-metadata-obp-metadata) 인스턴스와 [시각화](/docs/api/qiskit-addon-obp/utils-visualization#module-qiskit_addon_obp.utils.visualization) 모듈에서 제공하는 도구를 사용하여 역전파 프로세스를 시각화합니다.\n",
        "\n",
        "* **왼쪽 상단의 그래프에서** 볼 수 있듯이, 세 번째 슬라이스에 대한 역전파를 수행한 후 관측 가능한 항들을 잘라내기 시작할 만큼 충분한 예산이 확보된 것으로 나타납니다. 세 번째 슬라이스부터는, 각 슬라이스마다 역전파를 수행할 때마다 적어도 하나의 항이 잘려나간다는 것을 알 수 있습니다. 이는 각 슬라이스 처리 후 일정한 절단 오차가 발생하기 때문입니다.\n",
        "* **오른쪽 상단의 그래프는** 세 번째 슬라이스의 경우 `.003` 오차 예산이 까지 증가함을 보여줍니다. 잔여 예산이 급격히 감소한 것을 볼 수 있는데, 이는 관측 가능한 데이터에서 조건들이 생략되었음을 의미합니다. 이는 왼쪽 상단 그래프에서 우리가 추론한 내용과 일치합니다.\n",
        "* **왼쪽 하단의 그래프에서** 볼 수 있듯이, 관측 가능한 함수에서 항들을 제거해 나갈수록 전체 누적 오차는 단조 증가하는 양상을 보입니다. 이 그래프는 또한 세 번째 슬라이스에 대한 역전파가 완료될 때까지 어떤 항도 잘리지 않았음을 보여줍니다.\n",
        "* **오른쪽 하단의 그래프는** 우리 관측량에서 통근 파울리 군의 수가 지정된 한계인 까지 증가했음을 보여줍니다 `10`. 이 그래프는 또한 검은색 선과 빨간색 선이 교차하는 부분에서 볼 수 있듯이, 백프로파게이션을 한 층 더 수행하면 관측값이 지정된 범위를 벗어나게 된다는 점을 보여줍니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8ff436fa-3b52-4387-a9f4-d8d9df13c480",
      "metadata": {},
      "source": [
        "<div class=\"alert alert-info\">\n",
        "  이 모든 그래프에서 x축은 역전파된 슬라이스를 나열하고 있지만, OBP는 회로의 끝부분에서 작동하므로 `slice 1` 는 맨 마지막 슬라이스이고, `slice 2` 는 그 바로 앞의 슬라이스이며, 이와 같은 순서로 이어진다는 점에 유의하십시오.\n",
        "</div>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "68f118df-6718-49b2-ba80-c37ef461f71a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/68f118df-6718-49b2-ba80-c37ef461f71a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "from matplotlib import pyplot as plt\n",
        "from qiskit_addon_obp.utils.visualization import (\n",
        "    plot_accumulated_error,\n",
        "    plot_left_over_error_budget,\n",
        "    plot_num_qwc_groups,\n",
        "    plot_slice_errors,\n",
        ")\n",
        "\n",
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "523f5a81-afbc-4eee-9d66-05cc3b4a781a",
      "metadata": {},
      "source": [
        "<span id=\"specify-slice-budget-explicitly\" />\n",
        "\n",
        "## 슬라이스 예산을 명시적으로 지정합니다\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "80f9337d-ea37-454c-ae0b-b101259568c9",
      "metadata": {},
      "source": [
        "역전파 성능이 최적화되도록 각 슬라이스에 예산을 할당하는 방법을 알고 있다면, 각 슬라이스에 대해 예산을 명시적으로 할당하는 것이 좋습니다. 설명하기 위해, 처음 세 슬라이스에는 예산을 0으로 설정하고, 나머지 슬라이스에는 슬라이스당 `.001` 예산이 할당되도록 하겠습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "17ce7232-7e3a-44e4-8081-1b1b27430bad",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "# Zero out the first 3 slices' budgets\n",
        "max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)\n",
        "\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_per_slice=max_error_per_slice\n",
        ")\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "7cd711a7-1aea-4b45-a46a-63d771230969",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 11 circuit slices.\n",
            "New observable contains 32 terms and 10 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3e853723-454b-4c69-b9c5-180eb1b2e6c2",
      "metadata": {},
      "source": [
        "처음 세 계층에서 예산 할당을 제거한 결과, 네 번째 계층 이후까지는 어떤 용어도 잘리지 않았으며, 이는 이 그래프 중 세 개에서 확인할 수 있습니다. 다소 놀랍게도, 네 번째 구간에는 이월된 예산이 전혀 배정되지 않았음에도 불구하고, 할당된 예산을 `.001` 사용하여 한 학기가 단축되었습니다. 이는 **왼쪽** 상단 그래프에서 명백히 드러나지만, **오른쪽** 상단 그래프에서도 3번과 4번 슬라이스 사이에서 잔여 예산 곡선이 완만해지는 것을 확인할 수 있습니다. 또 다른 주목할 만한 점은, 이전 예시와 마찬가지로 이 시점부터 적어도 하나의 항이 잘려 나갔다는 것이다.\n",
        "\n",
        "핵심 요점은, 두 번째 예시에서는 일부 슬라이스의 예산을 0으로 설정함으로써 절단 오차가 줄어들긴 했지만, 동일한 수의 슬라이스에 대해 역전파를 수행할 수 있었으며, 관측 가능량에는 동일한 수의 통근 파울리 군이 포함되어 있다는 점입니다. **왼쪽 아래** 그래프를 살펴보면, 두 번째 예시에서 오차의 상한이 더 작다는 것을 확인할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "b7167f80-5d29-4c32-be97-cba733f18b1d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/b7167f80-5d29-4c32-be97-cba733f18b1d-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f03a92ee-e9a0-4707-b23a-ebdc3c9f35a3",
      "metadata": {},
      "source": [
        "<span id=\"specify-the-budget-cyclically\" />\n",
        "\n",
        "## 예산은 주기적으로 책정하십시오\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "62f0ea1f-eaec-408d-99df-25a8647c1eb9",
      "metadata": {},
      "source": [
        "트로터 회로와 같이 반복되는 패턴이 있는 회로의 경우, 해당 반복되는 슬라이스 하위 집합에 대한 예산을 지정하고, 그 예산이 해당 슬라이스의 이후 모든 반복에 사용되도록 할 수 있습니다.\n",
        "\n",
        "좀 더 구체적으로 말하자면, 우리가 사용하고 있는 예제 회로에는 6개의 슬라이스가 3번 반복되어 총 18개의 슬라이스가 있습니다. 단일 큐비트 층과 `RYY` 층에는 임의로 예산 0을 할당하고, `RXX` 층 각각에는 `.003` 예산씩 할당할 것입니다. 예산을 `length-6` 시퀀스로 지정하면, 예산이 18개의 모든 슬라이스에 순환적으로 적용되는 방식을 살펴보겠습니다.\n",
        "\n",
        "<div class=\"alert alert-info\">\n",
        "  다시 한 번, 슬라이스들에 대한 역전파가 역순으로(즉, 끝 부분부터 시작하여) 이루어진다는 점에 유의하십시오. 따라서, 순환 예산의 첫 번째 항목은 실제로 마지막 슬라이스에 사용되고, 두 번째 항목은 그 바로 앞의 슬라이스에 사용되며, 이와 같은 방식으로 이어집니다.\n",
        "</div>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4f37a576-5a61-4589-a896-8304056d0e19",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "# Specify a length-6 per-slice budget.\n",
        "# This will be cycled over three times to be applied to the 18 slices\n",
        "max_error_per_slice = [0.0] * 4 + [0.003] * 2\n",
        "\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_per_slice=max_error_per_slice\n",
        ")\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "7f78cfe4-13e4-472c-b6cc-9d1e867cbd9c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 13 circuit slices.\n",
            "New observable contains 49 terms and 14 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "op_budget = OperatorBudget(max_qwc_groups=20)\n",
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3511206b-2972-41ba-b981-6c20ccb7712b",
      "metadata": {},
      "source": [
        "**왼쪽** 상단과 **왼쪽** 하단 이미지에서 볼 수 있듯이, 처음 네 개의 슬라이스에는 오차 허용량이 할당되지 않았기 때문에 잘림 처리가 수행되지 않았습니다. 5번과 6번 단계에서는 예산이 확보됨에 따라 일부 용어가 축약되었습니다.\n",
        "\n",
        "**왼쪽** 상단 이미지에서 볼 수 있듯이, 7번 슬라이스에는 예산이 할당되지 않았음에도 불구하고, 해당 슬라이스에 대해 역전파를 수행한 후 비교적 큰 오차가 발생했습니다. 이는 5번과 6번 슬라이스에 배정된 총 `.006` 예산 중 `.002` 약 만 사용되었기 때문이며, 따라서 나머지 예산은 7번 슬라이스로 이관되어 **우측** 상단 이미지에서 볼 수 있듯이 대부분 소진되었기 때문입니다.\n",
        "\n",
        "**왼쪽** 상단과 **오른쪽** 상단의 이미지에서 볼 수 있듯이, 예상대로 소량의 잔여 예산은 8\\~10번 슬라이스 사이에서 소진되고, 11번 슬라이스에서는 새로운 예산이 할당됩니다. **왼쪽 위**, **오른쪽 위**, **왼쪽 아래의** 그림들은 모두 `max_error_per_slice` 인수의 길이가 슬라이스 수보다 작을 때 나타나는 주기적인 거동을 보여줍니다. 이러한 주기적인 현상은 모든 슬라이스에 걸쳐 계속되었을 것이지만, **오른쪽** 아래 이미지에서 볼 수 있듯이 13개의 슬라이스에 대해 역전파를 수행한 후 중단 `max_qwc_groups` 기준에 도달했습니다.\n",
        "\n",
        "또한 흥미로운 점은, 슬라이스 11에서 두 번째 예산 배정이 이루어진 후 파울리 그룹의 수가 실제로 감소한다는 사실입니다. 이는 계수가 작은 그룹들이 슬라이스 11에 대한 역전파가 완료되어 예산이 더 확보될 때까지 잘라낼 수 없었기 때문에, 여러 반복 과정에 걸쳐 관측 가능량에 누적되었기 때문입니다. 또한 이 사례는 알고리즘이 종료되기 위해서는 를 *초과* 해야 `max_qwc_groups` 한다는 점을 보여줍니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "4c36768e-03e7-4e1b-81b3-59b6e9eab43e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/4c36768e-03e7-4e1b-81b3-59b6e9eab43e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "269536c5-8ba4-48da-8e99-6b8ce3a52bd0",
      "metadata": {},
      "source": [
        "<span id=\"cap-the-total-error\" />\n",
        "\n",
        "## 총 오차 상한 설정\n",
        "\n",
        "슬라이스별 오차 허용 한도를 지정하는 것 외에도, 절단으로 인해 발생할 수 있는 최대 오차량을 지정할 수 있습니다. 해당 한도에 도달하면 더 이상 절삭 처리는 수행되지 않지만, 관측값이 너무 커져 중단 기준 중 하나가 충족될 때까지 역전파는 계속됩니다.\n",
        "\n",
        "다음으로, 오차 예산이 7번째 슬라이스의 역전파가 끝난 후에 소진되도록 `max_error_total` 에 상한을 설정하고, 위의 실험을 다시 수행합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "8ef6a100-fb4b-4b66-be6e-a094db2c29d4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "# Specify a length-6 per-slice budget.\n",
        "# This will be cycled over 3 times to be applied to the 18 slices\n",
        "max_error_per_slice = [0.0] * 4 + [0.003] * 2\n",
        "\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_per_slice=max_error_per_slice, max_error_total=0.006\n",
        ")\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "c58bccd5-aa40-482f-acaf-4ef50d420053",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 10 circuit slices.\n",
            "New observable contains 67 terms and 20 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dadb77a5-00d7-427d-921b-4a005f5ab36f",
      "metadata": {},
      "source": [
        "예상대로, 우리의 절단 오차는 ( `.006`\\*\\* 왼쪽 아래\\*\\* 그래프)에서 상한에 도달합니다. 이번 실행에서 슬라이스 11에 대해 역전파를 수행하지 못했다는 점이 주목할 만합니다. 이는 항을 잘라낼 만큼의 예산이 충분하지 않았고 `20`, **오른쪽** 아래 그림에서 볼 수 있듯이 통근 파울리 군의 수가 한계를 초과했기 때문입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "69c9069a-9038-4319-8dc6-37a1be973ebf",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/69c9069a-9038-4319-8dc6-37a1be973ebf-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2049d223-1b91-4042-ba98-a86f7a59483c",
      "metadata": {},
      "source": [
        "전체 예산에 상한선을 설정하는 것만으로도 충분할 수 있으며, 구체적인 금액을 명시하지 `max_error_total` 않아도 됩니다 `max_error_per_slice`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "fac59f4f-3528-49c1-9d4d-9be98d2c7d78",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "truncation_error_budget = setup_budget(max_error_total=0.018)\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "20c55e75-f619-4ac9-aecd-29491867ab82",
      "metadata": {},
      "source": [
        "위의 셀에 표시된 결과는 약간 의외일 수 있는데, 이는 가 로 설정되어 있기 `per_slice_budget` 때문입니다 `max_error_total`. 이는 사용 가능한 예산 전액이 **아낌없이** 소진될 것임을 시사합니다.\n",
        "이렇게 생각해 볼 수 있습니다. 각 슬라이스에는 전체 예산이 할당됩니다(왜냐하면 우리는 를 반복하기 때문입니다 `per_slice_budget`). 그러나 이미 소진된 예산은 알고리즘의 해당 시점에서 사용 가능한 예산에서 차감됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "5d65a057-eb5e-4308-b84d-82bfbea06989",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 9 circuit slices.\n",
            "New observable contains 25 terms and 9 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "op_budget = OperatorBudget(max_qwc_groups=10)\n",
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a77ceb1e-bc6d-462d-aebe-ec478d312233",
      "metadata": {},
      "source": [
        "**오른쪽 상단** 이미지는 전체 오차 `.018` 예산이 첫 번째 슬라이스에 어떻게 할당되는지를 보여줍니다. 세 번째 슬라이스까지는 잘림 처리가 이루어지지 않으므로, 남은 예산은 일정하게 유지됩니다. 예산은 소진될 때까지 각 역전파된 슬라이스에 전체 예산이 할당됨에 따라 단조 감소합니다.\n",
        "\n",
        "이 실험에서, 이 노트북의 첫 번째 실험과 거의 동일함에도 불구하고 역전파된 슬라이스 수가 두 개 더 적게 나온 점은 주목할 만하다. 이는 일부 문제의 경우, 예산을 균등하게 배분하는 것이 최선의 방법일 수 있음을 보여준다. 그 외의 문제의 경우, 슬라이스가 전체 예산을 최대한 많이 사용하도록 허용하면 더 나은 성능을 얻을 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "51d36d7e-0bd5-4388-bbb8-6831bf1f02e6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/51d36d7e-0bd5-4388-bbb8-6831bf1f02e6-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8859d36c-258f-441f-a7cf-3997b9f6e8ff",
      "metadata": {},
      "source": [
        "<span id=\"cap-the-number-of-backpropagated-slices-and-the-total-error-together\" />\n",
        "\n",
        "## 역전파되는 슬라이스 수와 총 오차를 함께 제한합니다\n",
        "\n",
        "오류 예산을 회로 전체에 걸쳐 분배하고 싶지는 않지만, 그렇다고 무분별하게 소모하고 싶지도 않은 경우, 역전파할 슬라이스 수(`num_slices`)와 총 오류 예산(`max_error_total`)을 함께 지정할 수 있습니다. 이렇게 하면 오류 예산이 (에 따라 `p_norm`) 입력 슬라이스 전체에 균등하게 분배됩니다.\n",
        "\n",
        "여기서는 역전파할 수 있는 슬라이스 수를 로 제한하고 `12`, 총 오차 예산은 그대로 유지하겠습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "474cf708-5447-4984-873c-5f62f81a402d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "num_slices = 12\n",
        "\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_total=0.018, num_slices=num_slices, p_norm=1\n",
        ")\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1ac2fb58-db97-4c9e-9a96-fce97b52e7e2",
      "metadata": {},
      "source": [
        "이제 이전 단계에서 예산을 할당했던 12개의 슬라이스에 대해 역전파를 시도해 보겠습니다. 이를 위해 회로에 마지막 12개의 슬라이스만 전달하면 됩니다. 의 경우 `p_norm=1`, 12개의 슬라이스 각각에 할당 가능한 예산은 입니다 `0.018 / num_slices = 0.0015`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "6efc92bc-3433-4ea6-a683-684205201f4f",
      "metadata": {},
      "outputs": [],
      "source": [
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices[-num_slices:],\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b15c2a20-19c3-417c-b32d-173c10f13852",
      "metadata": {},
      "source": [
        "(코드 내 `slices[-num_slices:]`) 슬라이스의 일부를 에 전달했기 때문에 `backpropagate`, 역전파 후 남은 슬라이스를 역전파에 전달되지 않았던 슬라이스(코드 내) `slices[:-num_slices]`와 결합해야 합니다.\n",
        "\n",
        "남은 모든 슬라이스를 결합한 후에는 [combine\\_slices를](/docs/api/qiskit-addon-utils/slicing#combine_slices) 사용하여 깊이가 축소된 [QuantumCircuit](/docs/api/qiskit/qiskit.circuit.QuantumCircuit) 를 생성할 수 있습니다. 다음으로, 관측 가능한 변수의 크기가 얼마나 커졌는지에 비해 역전파된 슬라이스가 몇 개나 되는지 살펴봅니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "7c42256f-e7f2-40ed-999e-6a51fd9351c4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 12 circuit slices.\n",
            "New observable contains 29 terms and 9 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "# Recombine the slices remaining after backprop with the rest of the original circuit\n",
        "reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)\n",
        "\n",
        "print(f\"Backpropagated {num_slices - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ae2d383c-c11c-457e-856f-85602f2df306",
      "metadata": {},
      "source": [
        "그래프를 보면, 관측 가능한 변수를 10개의 통행 가능한 파울리 군 이내로 유지하면서 12개의 슬라이스 모두에 대해 역전파를 성공적으로 수행했음을 알 수 있습니다. 또한 와 를 함께 `num_slices` 사용하면 슬라이스 간에 예산이 분배되며 `max_error_total` , 미사용 예산은 다시 다음 슬라이스로 이월된다는 것을 알 수 있습니다. 이는 **오른쪽** 상단의 그래프에서 가장 뚜렷하게 드러나는데, 역전파 과정 전반에 걸쳐 예산이 소진되기도 하고 다시 보충되기도 하기 때문이다.\n",
        "\n",
        "이 오차 분배 방식이 이 노트북의 첫 번째 실험 및 바로 앞 예제와 비교했을 때 가장 좋은 결과(역전파된 슬라이스 수가 더 많음)를 보였다는 점은 주목할 만합니다. 이 모든 예시에서는 오차 `.018` 예산을 할당했지만, 그 예산이 어떻게 분배되었는지에 따라 역전파의 수행 방식이 달랐습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "b0bf5050-2928-41a1-9c3d-c9d79e918f6f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/b0bf5050-2928-41a1-9c3d-c9d79e918f6f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5c554774-2986-43a0-bdcd-45eaeb81ef5a",
      "metadata": {},
      "source": [
        "<span id=\"work-with-multiple-observables\" />\n",
        "\n",
        "## 여러 관측 가능 객체와 함께 작업하기\n",
        "\n",
        "이 `qiskit_addon_obp.backpropagate` 메서드를 사용하면 일련의 관측 가능 객체를 전달할 수 있습니다. 이를 통해 여러 대상 관측량을 다룰 때의 작업 흐름이 간소화됩니다.\n",
        "\n",
        "이곳에서 이를 명시적으로 언급하는 이유는, 잘림 전략이 이러한 경우를 어떻게 처리하는지 설명해 드리기 위함입니다. 이 예제를 위해, 지금까지 사용해 온 관측 가능 변수에 하나 더 추가합니다:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "0857ea4c-82c1-4641-a268-1a9ba44a312b",
      "metadata": {},
      "outputs": [],
      "source": [
        "obs = [SparsePauliOp(\"IIIIIZIIII\"), SparsePauliOp(\"IIIIIXIIII\")]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba3ac81c-7395-4287-907a-742a1751cbd0",
      "metadata": {},
      "source": [
        "마지막으로, 이 튜토리얼의 첫 번째 실험을 다시 수행합니다., 하지만 역전파를 수행할 두 개의 관측량이 있는 회로입니다.\n",
        "\n",
        "이 예제의 경우, 이는 역전파될 수 있는 슬라이스의 수에는 영향을 미치지 않습니다. 그러나 두 관측량에서는 파울리 항의 개수와 교환군의 개수가 서로 달랐음을 알 수 있다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "10c50f14-5bbd-427d-ae77-24389498a1e4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)\n"
          ]
        }
      ],
      "source": [
        "truncation_error_budget = setup_budget(max_error_per_slice=0.001)\n",
        "print(truncation_error_budget)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "862edaec-fc2c-488b-875d-23841ecc80b3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 11 circuit slices.\n",
            "The new first observable contains 29 terms and 10 commuting groups.\n",
            "The new second observable contains 23 terms and 8 commuting groups.\n"
          ]
        }
      ],
      "source": [
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    obs,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "reduced_circuit = combine_slices(remaining_slices)\n",
        "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
        "print(\n",
        "    f\"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")\n",
        "print(\n",
        "    f\"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "467c614d-6bac-4826-a42b-39937b9d0c72",
      "metadata": {},
      "source": [
        "아래의 그림들은 역전파 알고리즘이 여러 관측 변수를 어떻게 처리하는지 설명해 줍니다.\n",
        "\n",
        "먼저, **왼쪽 위**, **오른쪽 위**, **왼쪽 아래** 의 그래프에서 볼 수 있듯이, 항을 생략하기 위한 예산은 관측 변수마다 개별적으로 설정됩니다. 다시 말해, 두 관측 가능량 모두 역전파된 슬라이스당 `0.001` 의 오차를 가정하여 항을 잘라낼 수 있는 능력을 가지고 있다.\n",
        "관측 가능한 변수들의 특성이 서로 다르기 때문에, 이로 인해 예산 소모량에도 차이가 발생합니다. 이 예시에서는 두 가지가 상당히 겹치는 모습을 보이지만, 항상 그런 것은 아닙니다.\n",
        "\n",
        "**오른쪽 아래의** 그래프는 이 함수가 *모든* 관측 가능한 변수를 모두 고려하고 `max_qwc_groups` 있음을 보여줍니다. 즉, 모든 관측 가능한 양의 항들을 한데 묶어 큐비트 단위로 교환하는 군의 최종 개수를 산출한 뒤, 이를 와 비교합니다 `max_qwc_paulis`. `max_paulis` 임계값(이 노트북에서는 다루지 않음)에 대해서도 동일한 처리가 이루어지며, 이를 통해 모든 관측량에 걸친 파울리 항의 개수에 제한을 설정할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "82271b0d-ddaf-4ad2-b5c6-e9e4c2a4cff7",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-obp/guides/truncate-operator-terms/extracted-outputs/82271b0d-ddaf-4ad2-b5c6-e9e4c2a4cff7-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
        "plot_slice_errors(metadata, axes[(0, 0)])\n",
        "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
        "plot_accumulated_error(metadata, axes[(1, 0)])\n",
        "plot_num_qwc_groups(metadata, axes[(1, 1)])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}