{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "d2c31ae8",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"期待値推定のための演算子バックプロパゲーション（OBP）\"\n",
        "description: \"このチュートリアルでは、qiskit-addon-obp を使用してハイゼンベルグ・スピン鎖の量子ダイナミクスをシミュレートする Qiskit パターンを実装します\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore fontsize edgecolor circo simeq */}\n",
        "\n",
        "<span id=\"operator-backpropagation-obp-for-estimation-of-expectation-values\" />\n",
        "\n",
        "# 期待値推定のための演算子バックプロパゲーション（OBP）\n",
        "\n",
        "*推定所要時間：Heron r3 プロセッサで 4 分（注：これはあくまで目安です。 （実行時間は異なる場合があります。）*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8bf80006",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 学習成果\n",
        "\n",
        "このチュートリアルを終えた後、ユーザーは以下の点を理解できるようになります：\n",
        "\n",
        "* 回路の実行回数を増やす代わりに、量子回路の深さを減らす方法 [`qiskit-addon-obp`](https://github.com/Qiskit/qiskit-addon-obp)\n",
        "* XYZハミルトニアンとその時間発展回路を構築する方法 [`qiskit-addon-utils`](https://github.com/Qiskit/qiskit-addon-utils)\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 前提条件\n",
        "\n",
        "このチュートリアルを進める前に、以下の内容についてあらかじめ理解しておいていただくことをお勧めします：\n",
        "\n",
        "* [Estimatorプリミティブ](/docs/api/qiskit-ibm-runtime/estimator-v2)を使用して観測変数の期待値を計算する\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## 背景\n",
        "\n",
        "演算子のバックプロパゲーションとは、量子回路の末尾にある演算を測定される観測量に吸収させる手法であり、一般に、観測量に追加項が生じる代償として、回路の深さを浅くすることができる。 目標は、観測可能な変数が過度に大きくなるのを防ぎつつ、回路の可能な限り多くの部分をバックプロパゲーションさせることである。 OBP Qiskit アドオンには、Qiskit ベースの実装が含まれています。 詳細については、関連する[ドキュメント](https://qiskit.github.io/qiskit-addon-obp/)をご参照ください。\n",
        "\n",
        "観測量 $O = \\sum_P c_P P$ を測定する回路の例を考えてみよう。ここで、 $P$ はパウリ演算子であり、 $c_P$ は係数である。 この回路を単一のユニタリー $U$ と表すことにする。これは、下図に示すように、 $U = U_C U_Q$ へと論理的に分割することができる。\n",
        "\n",
        "![Uqに続くUcを示す回路図](https://quantum.cloud.ibm.com/docs/images/tutorials/improving-estimation-of-expectation-values-with-operator-backpropagation/logical-partitioning.avif)\n",
        "\n",
        "演算子のバックプロパゲーションでは、ユニタリー演算 $U_C$ を観測量に吸収し、 $O' = U_C^{\\dagger}OU_C = \\sum_P c_P U_C^{\\dagger}PU_C$ として進化させる。言い換えれば、計算の一部は、観測量が $O$ から $O'$ へと進化する過程を通じて、古典的に実行される。これにより、元の問題は、ユニタリー演算が $U_Q$ である新しい低深さの回路について、観測量 $O'$ を測定するという問題として再定式化できる。\n",
        "\n",
        "単一の $U_C$ は、複数のスライス $U_C = U_S U_{S-1}...U_2U_1$ として表現されます。スライスを定義する方法はいくつかあります。 例えば、上記の回路例において、 $R_{zz}$ の各層および $R_x$ ゲートの各層は、それぞれ独立したスライスと見なすことができます。 バックプロパゲーションでは、従来、 $O' = \\Pi_{s=1}^S \\sum_P c_P U_s^{\\dagger} P U_s$ の計算が行われます。 各スライス $U_s$ は、 $U_s = exp(\\frac{-i\\theta_s P_s}{2})$ と表すことができる。ここで、 $P_s$ は $n$ -量子ビットのパウリ演算子であり、 $\\theta_s$ はスカラーである。 これは簡単に確認できます\n",
        "\n",
        "$$\n",
        "U_s^{\\dagger} P U_s = P \\qquad \\text{if} ~[P,P_s] = 0,\n",
        "$$\n",
        "\n",
        "$$\n",
        "U_s^{\\dagger} P U_s = \\qquad cos(\\theta_s)P + i sin(\\theta_s)P_sP \\qquad \\text{if} ~\\{P,P_s\\} = 0\n",
        "$$\n",
        "\n",
        "上記の例において、 $\\{P,P_s\\} = 0$ である場合、期待値を計算するには、1つではなく2つの量子回路を実行する必要があります。 したがって、バックプロパゲーションによって観測可能関数の項の数が増加し、その結果、回路の実行回数が増える可能性がある。 演算子の規模が過度に大きくなるのを防ぎつつ、回路へのバックプロパゲーションをより深く行うための方法の一つは、係数の小さい項を演算子に加えるのではなく、切り捨てることである。 例えば、上記の例では、 $\\theta_s$ が十分に小さいという条件の下で、 $P_sP$ を含む項を切り捨てることもできる。 項を切り捨てると、実行すべき量子回路の数が減るが、その代償として、切り捨てられた項の係数の大きさに比例した誤差が、最終的な期待値の計算に生じる。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55b94021",
      "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",
        "* OBP Qiskit アドオン 0.3 以降 (`pip install qiskit-addon-obp`)\n",
        "* Qiskit アドオン utils 0.3 以降 (`pip install qiskit-addon-utils`)\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7db2e559",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## セットアップ\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "bc380c46",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "from qiskit.primitives import StatevectorEstimator\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.transpiler import CouplingMap\n",
        "from qiskit.synthesis import LieTrotter\n",
        "\n",
        "from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian\n",
        "from qiskit_addon_utils.problem_generators import (\n",
        "    generate_time_evolution_circuit,\n",
        ")\n",
        "from qiskit_addon_utils.slicing import slice_by_depth, combine_slices\n",
        "from qiskit_addon_obp.utils.simplify import OperatorBudget\n",
        "from qiskit_addon_obp import backpropagate\n",
        "from qiskit_addon_obp.utils.truncating import setup_budget\n",
        "\n",
        "from rustworkx.visualization import graphviz_draw\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import EstimatorV2, EstimatorOptions"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "431a5bd2-e6ed-471b-ad9e-c4edd27784a8",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## 小規模シミュレータの例\n",
        "\n",
        "このチュートリアルでは[、OBP Qiskitアドオン](https://github.com/Qiskit/qiskit-addon-obp)を使用して、ハイゼンベルグスピン鎖の量子ダイナミクスをシミュレート[するQiskit](/docs/guides/intro-to-patterns) パターンを実装します。 なお、ノイズのないシミュレータでは、バックプロパゲーションを行う場合と行わない場合で得られる期待値は同じになります。\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "988ee237",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### ステップ1：古典的な入力を量子問題にマッピングする\n",
        "\n",
        "<span id=\"map-the-time-evolution-of-a-quantum-heisenberg-model-to-a-quantum-experiment\" />\n",
        "\n",
        "#### 量子ハイゼンベルクモデルの時間発展を量子実験に写像する\n",
        "\n",
        "まず、から `qiskit-addon-utils` 関数を [`generate_xyz_hamiltonian`](/docs/api/qiskit-addon-utils/problem-generators#generate_xyz_hamiltonian) 用いて、与えられた接続グラフ上でハイゼンベルク型のハミルトニアンを生成する。 このグラフは、 [rustworkx.PyGraph](https://www.rustworkx.org/apiref/rustworkx.PyGraph.html) または [CouplingMap](/docs/api/qiskit/qiskit.transpiler.CouplingMap) のいずれかです。 以下では、10個の量子ビットからなる線形チェーン `CouplingMap` を使用する。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "a3debf65-06df-4277-933e-14b6f6170756",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/operator-back-propagation/extracted-outputs/a3debf65-06df-4277-933e-14b6f6170756-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "num_qubits = 10\n",
        "layout = [(i - 1, i) for i in range(1, num_qubits)]\n",
        "\n",
        "# Instantiate a CouplingMap object\n",
        "coupling_map = CouplingMap(layout)\n",
        "graphviz_draw(coupling_map.graph, method=\"circo\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ef13f367",
      "metadata": {},
      "source": [
        "次に、ハイゼンベルクXYZハミルトニアンをモデル化するパウリ演算子を生成します：\n",
        "\n",
        "$$\n",
        "{\\hat{\\mathcal{H}}_{XYZ} = \\sum_{(j,k)\\in E} (J_{x} \\sigma_j^{x} \\sigma_{k}^{x} + J_{y} \\sigma_j^{y} \\sigma_{k}^{y} + J_{z} \\sigma_j^{z} \\sigma_{k}^{z}) + \\sum_{j\\in V} (h_{x} \\sigma_j^{x} + h_{y} \\sigma_j^{y} + h_{z} \\sigma_j^{z}),}\n",
        "$$\n",
        "\n",
        "ここで、 $G(V,E)$ は結合写像のグラフである。 このチュートリアルでは、 $J_x, J_y, J_z$ を $\\frac{\\pi}{8}, \\frac{\\pi}{4}, \\frac{\\pi}{2}$、 $h_x, h_y, h_z$ を $\\frac{\\pi}{3}, \\frac{\\pi}{6}, \\frac{\\pi}{9}$ として使用しています。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "b0f1e5fd",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII', 'IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIYII', 'IIIIIIIZII', 'IIIIIIXIII', 'IIIIIIYIII', 'IIIIIIZIII', 'IIIIIXIIII', 'IIIIIYIIII', 'IIIIIZIIII', 'IIIIXIIIII', 'IIIIYIIIII', 'IIIIZIIIII', 'IIIXIIIIII', 'IIIYIIIIII', 'IIIZIIIIII', 'IIXIIIIIII', 'IIYIIIIIII', 'IIZIIIIIII', 'IXIIIIIIII', 'IYIIIIIIII', 'IZIIIIIIII', 'XIIIIIIIII', 'YIIIIIIIII', 'ZIIIIIIIII'],\n",
            "              coeffs=[0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,\n",
            " 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,\n",
            " 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,\n",
            " 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,\n",
            " 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,\n",
            " 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,\n",
            " 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 1.04719755+0.j,\n",
            " 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,\n",
            " 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,\n",
            " 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,\n",
            " 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,\n",
            " 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,\n",
            " 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,\n",
            " 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,\n",
            " 0.34906585+0.j])\n"
          ]
        }
      ],
      "source": [
        "# Get a qubit operator describing the Heisenberg XYZ model\n",
        "hamiltonian = generate_xyz_hamiltonian(\n",
        "    coupling_map,\n",
        "    coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),\n",
        "    ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),\n",
        ")\n",
        "print(hamiltonian)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a81f0346",
      "metadata": {},
      "source": [
        "量子ビット演算子から、その時間発展をモデル化する量子回路を生成することができる。 我々は、リー・トロッター分解を用いて時間発展回路を構築した [`generate_time_evolution_circuit`](/docs/api/qiskit-addon-utils/problem-generators#generate_time_evolution_circuit) 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "5208e0a8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/operator-back-propagation/extracted-outputs/5208e0a8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "circuit = generate_time_evolution_circuit(\n",
        "    hamiltonian,\n",
        "    time=0.2,\n",
        "    synthesis=LieTrotter(reps=2),\n",
        ")\n",
        "circuit.draw(\"mpl\", style=\"iqp\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ac6f36e3",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### ステップ2：量子ハードウェア実行に向けた問題の最適化\n",
        "\n",
        "<span id=\"create-circuit-slices-to-backpropagate\" />\n",
        "\n",
        "#### バックプロパゲーションを行うための回路スライスを作成する\n",
        "\n",
        "この `backpropagate` 関数は、回路のスライスを一度にまとめてバックプロパゲーションを行います。 したがって、スライシングの選択は、特定の問題においてバックプロパゲーションの性能にどのような影響を与えるかに関わってくる。 ここでは、関数 [`slice_by_depth`](/docs/api/qiskit-addon-utils/slicing#slice_by_depth) を使用して、同じタイプのゲートをスライスごとにグループ化します。\n",
        "\n",
        "サーキットスライシングについてさらに詳しく知りたい方は、こちらのパッケージ [`qiskit-addon-utils`](https://github.com/Qiskit/qiskit-addon-utils) の[ハウツーガイド](https://qiskit.github.io/qiskit-addon-utils/how_tos/create_circuit_slices.html)をご覧ください。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "1834cb22",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Separated the circuit into 18 slices.\n"
          ]
        }
      ],
      "source": [
        "slices = slice_by_depth(circuit, max_slice_depth=1)\n",
        "print(f\"Separated the circuit into {len(slices)} slices.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1df16e6b",
      "metadata": {},
      "source": [
        "<span id=\"constrain-how-large-the-operator-can-grow-during-backpropagation\" />\n",
        "\n",
        "#### バックプロパゲーション中にオペレータのサイズがどれほど大きくなるかを制限する\n",
        "\n",
        "バックプロパゲーション中、演算子の項の数は、一般的に $2^L$ に急速に近づきます。ここで、 $L$ はスライスの数です。 演算子内の2つの項が量子ビットごとに可換でない場合、それらに対応する期待値を求めるには、それぞれ別の回路が必要となる。 例えば、2量子ビットの観測量 $O = 0.1 XX + 0.3 IZ - 0.5 IX$ がある場合、 $[XX,IX] = 0$ であるため、単一の基底での測定を行うだけで、これら2つの項の期待値を計算するのに十分である。 しかし、 $IZ$ は他の2つの項と反交換するため、 $IZ$ の期待値を計算するには、別の基底測定が必要となる。つまり、 $\\langle O \\rangle$ を計算するには、1つではなく2つの回路が必要となる。演算子の項の数が増えるにつれて、必要な回路の実行回数も増える可能性がある。\n",
        "\n",
        "演算子のサイズは、 `backpropagate` 関数の `operator_budget` kwarg を指定することで制限できる。 [OperatorBudget](/docs/api/qiskit-addon-obp/utils-simplify#operatorbudget) インスタンスを受け取る。\n",
        "\n",
        "割り当てられる追加リソースの量（回路の実行回数、ひいては必要なQPU時間）を制御するため、バックプロパゲーションされた観測量が持つことができるクビットごとの可換パウリ群の最大数を制限する。 ここでは、演算子に含まれるクビットごとの可換パウリ群の数が8を超えた時点で、バックプロパゲーションを停止させることを規定する。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "3074b173",
      "metadata": {},
      "outputs": [],
      "source": [
        "op_budget = OperatorBudget(max_qwc_groups=8)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8bd6ac5f",
      "metadata": {},
      "source": [
        "<span id=\"backpropagate-slices-from-the-circuit\" />\n",
        "\n",
        "#### 回路からのスライスをバックプロパゲートする\n",
        "\n",
        "$M_Z = \\frac{1}{N} \\sum_{i=1}^N \\langle Z_i \\rangle$、 $N$ は量子ビットの数である。 時間発展回路からのスライスをバックプロパゲートし、観測可能な項が8量子ビット以下のパウリ群にまとめられなくなるまでバックプロパゲートする。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "a1300365",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "SparsePauliOp(['IIIIIIIIIZ', 'IIIIIIIIZI', 'IIIIIIIZII', 'IIIIIIZIII', 'IIIIIZIIII', 'IIIIZIIIII', 'IIIZIIIIII', 'IIZIIIIIII', 'IZIIIIIIII', 'ZIIIIIIIII'],\n",
              "              coeffs=[0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j,\n",
              " 0.1+0.j, 0.1+0.j])"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    [(\"Z\", [i], 1 / num_qubits) for i in range(num_qubits)],\n",
        "    num_qubits=num_qubits,\n",
        ")\n",
        "observable"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "820236bf",
      "metadata": {},
      "source": [
        "以下に示すように、6つのスライスに対してバックプロパゲーションを行い、項は8つではなく6つのグループにまとめられました。 これは、スライスをもう1つバックプロパゲーションすると、パウリ群の数が8を超えることになることを意味する。 返されたメタデータを調べれば、これが事実であることを確認できます。 なお、この部分では回路の変換が厳密である点にも留意されたい。  つまり、新しい観測量 $O’$ の項は、いずれも切り捨てられていない。 バックプロパゲーションされた回路と演算子は、元の回路および演算子とまったく同じ結果を与える。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ee8fd385",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backpropagated 6 slices.\n",
            "New observable has 60 terms, which can be combined into 6 groups.\n",
            "Note that backpropagating one more slice would result in 114 terms across 12 groups.\n",
            "The remaining circuit after backpropagation looks as follows:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/operator-back-propagation/extracted-outputs/ee8fd385-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Backpropagate slices onto the observable\n",
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    observable, slices, operator_budget=op_budget\n",
        ")\n",
        "# Recombine the slices remaining after backpropagation\n",
        "bp_circuit = combine_slices(remaining_slices)\n",
        "\n",
        "print(f\"Backpropagated {metadata.num_backpropagated_slices} slices.\")\n",
        "print(\n",
        "    f\"New observable has {len(bp_obs.paulis)} terms, which can be combined into \"\n",
        "    f\"{len(bp_obs.group_commuting(qubit_wise=True))} groups.\"\n",
        ")\n",
        "print(\n",
        "    f\"Note that backpropagating one more slice would result in \"\n",
        "    f\"{metadata.backpropagation_history[-1].num_paulis[0]} terms \"\n",
        "    f\"across {metadata.backpropagation_history[-1].num_qwc_groups} groups.\"\n",
        ")\n",
        "print(\"The remaining circuit after backpropagation looks as follows:\")\n",
        "bp_circuit.draw(\"mpl\", fold=-1, scale=0.6)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "833d9c36",
      "metadata": {},
      "source": [
        "シミュレータでの小規模な例については、切り捨ては行いません。 これは、ノイズがない場合、バックプロパゲーションを行う回路と行わない回路では同じ結果が得られるためであり、切り捨てを行うと近似が加わることで結果が悪化するためです。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "43f58cfb",
      "metadata": {},
      "source": [
        "<span id=\"transpile-the-circuits-into-the-basis-gate-set\" />\n",
        "\n",
        "#### 回路を基本ゲートセットに変換する\n",
        "\n",
        "ここで、元の回路とバックプロパゲーションされた回路の両方を、バックエンドのベースゲートにトランスパイルします。 小規模なインスタンスではシミュレータ上で実行するため、実際のバックエンドでトランスパイルを行う必要はありません。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "29d71cd3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "<IBMBackend('ibm_kingston')>\n"
          ]
        }
      ],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=133\n",
        ")\n",
        "print(backend)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "18707b33",
      "metadata": {},
      "outputs": [],
      "source": [
        "pm_basis = generate_preset_pass_manager(\n",
        "    optimization_level=3, basis_gates=backend.configuration().basis_gates\n",
        ")\n",
        "isa_circuit = pm_basis.run(circuit)\n",
        "isa_bp_circuit = pm_basis.run(bp_circuit)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "b4d480b3",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### ステップ3: `Qiskit primitives`を使用して実行する\n",
        "\n",
        "まず、元の回路とバックプロパゲーションされた回路に対応する [2](/docs/api/qiskit/primitives) つのプリミティブ・ユニファイド・ブロック（PUB）を作成します。 次に、理想的な推定器を用いて確率分布を評価し、期待値を求めます。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "b22a1b00",
      "metadata": {},
      "outputs": [],
      "source": [
        "pubs = [(isa_circuit, observable), (isa_bp_circuit, bp_obs)]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "eb174b15",
      "metadata": {},
      "outputs": [],
      "source": [
        "rng = np.random.default_rng()\n",
        "estimator = StatevectorEstimator(seed=rng)\n",
        "job = estimator.run(pubs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50b94af2",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### ステップ4：後処理を行い、希望する従来の形式で結果を返す\n",
        "\n",
        "ここで、元の回路とバックプロパゲーションを施した回路の期待値を求めます。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "31dc35ea-6554-4ca7-9c3b-0b5394c46e4e",
      "metadata": {},
      "outputs": [],
      "source": [
        "primitive_result = job.result()\n",
        "circuit_expval = primitive_result[0].data.evs.item()\n",
        "bp_circuit_expval = primitive_result[1].data.evs.item()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "fb5f955a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "Text(0, 0.5, '$M_Z$')"
            ]
          },
          "execution_count": 14,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/operator-back-propagation/extracted-outputs/fb5f955a-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "methods = [\n",
        "    \"No backpropagation\",\n",
        "    \"Backpropagation\",\n",
        "]\n",
        "values = [circuit_expval, bp_circuit_expval]\n",
        "\n",
        "ax = plt.gca()\n",
        "plt.bar(methods, values, color=\"#a56eff\", width=0.4, edgecolor=\"#8a3ffc\")\n",
        "ax.set_ylim([0.6, 0.92])\n",
        "ax.set_ylabel(r\"$M_Z$\", fontsize=12)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c0be738f",
      "metadata": {},
      "source": [
        "予想通り、2つの期待値は一致している。 ノイズのない状態ベクトルシミュレータ上で実行しているため、バックプロパゲーションは回路・観測値ペアに対する厳密な変換となります。したがって、元のワークフローとバックプロパゲーションを適用したワークフローは、 $M_Z$ に対して同じ値を算出する必要があります。バックプロパゲーションの利点は、ノイズのあるハードウェア上で初めて明らかになります。そこでは、バックプロパゲーションを適用した回路の方が短いため、蓄積される誤差が少なくなります。これは、以下の大規模ハードウェアの例で示されている通りです。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0d6db390-e7a8-4efe-902c-8d9a312170c6",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## 大規模なハードウェアの例\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ae69c5e0-32b1-4f03-ab13-7b95a9acfd25",
      "metadata": {},
      "source": [
        "実験を設計する際は、可視化やシミュレーションを容易にするために、まずは簡単な回路から始めるのが有効です。 ここでは、 $J$ および $h$ パラメータの値、ならびに観測量 $M_Z$ を同じに設定した50キュービットのハイゼンベルク・ハミルトニアンについて、4回のトロッター法を用いた演算子のバックプロパゲーションを検討する。 このスケールにおける理想的な期待値は、力ずくの計算では求められないため、テンソルネットワークを用いて計算した結果、理想的な期待値は $\\simeq 0.89$ であることが得られた。\n",
        "\n",
        "この大規模な例では、バックプロパゲーションに加え、切り捨て付きバックプロパゲーションについても紹介します。 理想的には、有効回路の深さを減らすために、可能な限りバックプロパゲーションを行いたい。 しかし、その結果、更新後の観測可能量に非通勤項が大量に含まれることになり、量子オーバーヘッドが増大してしまうことがよくある。 したがって、係数が小さい項については、「切り捨て」と呼ばれる手法を用いて除外することができます。 切り捨てを行うと、更新後の観測量の項の数が減るため、伝播範囲は広がるが、その一方で、ある程度の近似が生じる。 したがって、より深いバックプロパゲーションによって得られるノイズ低減効果が、近似誤差によって相殺されないよう、切り捨てを一定の範囲内に制限する必要がある。\n",
        "\n",
        "切り捨ての量を制限するため、関数 [`setup_budget`](/docs/api/qiskit-addon-obp/utils-truncating#setup_budget) を使用して、各スライスごとにエラー予算を割り当てるとともに、バックプロパゲーションされた回路全体に対する総エラー予算を設定する。 これにより、回路全体だけでなく、各スライスごとに切り捨てが適切に制御されるようになります。 予算の配分方法については、こちらの[ガイド](https://qiskit.github.io/qiskit-addon-obp/how_tos/truncate_operator_terms.html)もご参照ください。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "28ac4dbf",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "2-qubit depth without backpropagation: 24\n",
            "2-qubit depth with backpropagation: 20\n",
            "2-qubit depth with backpropagation and truncation: 18\n"
          ]
        }
      ],
      "source": [
        "num_qubits = 50\n",
        "layout = [(i - 1, i) for i in range(1, num_qubits)]\n",
        "\n",
        "# Instantiate a CouplingMap object\n",
        "coupling_map = CouplingMap(layout)\n",
        "\n",
        "hamiltonian = generate_xyz_hamiltonian(\n",
        "    coupling_map,\n",
        "    coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),\n",
        "    ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),\n",
        ")\n",
        "\n",
        "# Generate a time evolution circuit for the Hamiltonian\n",
        "circuit = generate_time_evolution_circuit(\n",
        "    hamiltonian,\n",
        "    time=0.2,\n",
        "    synthesis=LieTrotter(reps=4),\n",
        ")\n",
        "\n",
        "# Define the observable to measure\n",
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    [(\"Z\", [i], 1 / num_qubits) for i in range(num_qubits)],\n",
        "    num_qubits,\n",
        ")\n",
        "\n",
        "slices = slice_by_depth(circuit, max_slice_depth=1)\n",
        "\n",
        "# Define the maximum number of qwc groups allowed in the\n",
        "# backpropagated observable,\n",
        "# and the truncation error budget\n",
        "op_budget = OperatorBudget(max_qwc_groups=15)\n",
        "truncation_error_budget = setup_budget(\n",
        "    max_error_total=0.03, max_error_per_slice=0.005\n",
        ")\n",
        "\n",
        "# First backpropagation without truncation\n",
        "bp_obs, remaining_slices, metadata = backpropagate(\n",
        "    observable, slices, operator_budget=op_budget\n",
        ")\n",
        "bp_circuit = combine_slices(remaining_slices)\n",
        "\n",
        "# Now backpropagate with truncation, using the same operator budget and\n",
        "# the defined truncation error budget\n",
        "bp_obs_trunc, remaining_slices_trunc, metadata = backpropagate(\n",
        "    observable,\n",
        "    slices,\n",
        "    operator_budget=op_budget,\n",
        "    truncation_error_budget=truncation_error_budget,\n",
        ")\n",
        "bp_circuit_trunc = combine_slices(\n",
        "    remaining_slices_trunc, include_barriers=False\n",
        ")\n",
        "\n",
        "# Now we transpile the original circuit and the two backpropagated circuits,\n",
        "# and apply the layout to the corresponding observables\n",
        "pm = generate_preset_pass_manager(optimization_level=3, backend=backend)\n",
        "\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_bp_circuit = pm.run(bp_circuit)\n",
        "isa_bp_circuit_trunc = pm.run(bp_circuit_trunc)\n",
        "\n",
        "isa_observable = observable.apply_layout(isa_circuit.layout)\n",
        "isa_bp_observable = bp_obs.apply_layout(isa_bp_circuit.layout)\n",
        "isa_bp_observable_trunc = bp_obs_trunc.apply_layout(\n",
        "    isa_bp_circuit_trunc.layout\n",
        ")\n",
        "\n",
        "# Compare the 2-qubit depth of each transpiled circuit to see how much\n",
        "# depth backpropagation saved\n",
        "print(\n",
        "    f\"2-qubit depth without backpropagation: \"\n",
        "    f\"{isa_circuit.depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "print(\n",
        "    f\"2-qubit depth with backpropagation: \"\n",
        "    f\"{isa_bp_circuit.depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "print(\n",
        "    f\"2-qubit depth with backpropagation and truncation: \"\n",
        "    f\"{isa_bp_circuit_trunc.depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "\n",
        "pubs = [\n",
        "    (isa_circuit, isa_observable),\n",
        "    (isa_bp_circuit, isa_bp_observable),\n",
        "    (isa_bp_circuit_trunc, isa_bp_observable_trunc),\n",
        "]\n",
        "\n",
        "# Now we instantiate the Estimator primitive for the hardware with\n",
        "# ZNE and measurement error\n",
        "# mitigation and compute the three circuits and observables\n",
        "options = EstimatorOptions()\n",
        "options.default_precision = 0.01\n",
        "options.resilience_level = 2\n",
        "options.resilience.zne.noise_factors = [1, 1.2, 1.4]\n",
        "options.resilience.zne.extrapolator = [\"linear\"]\n",
        "estimator = EstimatorV2(mode=backend, options=options)\n",
        "\n",
        "estimator.options.environment.job_tags = [\"TUT_OBP\"]\n",
        "job = estimator.run(pubs)\n",
        "\n",
        "# Retrieve the results and the standard deviations\n",
        "result_no_bp = job.result()[0].data.evs.item()\n",
        "result_bp = job.result()[1].data.evs.item()\n",
        "result_bp_trunc = job.result()[2].data.evs.item()\n",
        "\n",
        "std_no_bp = job.result()[0].data.stds.item()\n",
        "std_bp = job.result()[1].data.stds.item()\n",
        "std_bp_trunc = job.result()[2].data.stds.item()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "4a0155bf",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expectation value without backpropagation: 0.9543907942381811\n",
            "Backpropagated expectation value: 0.9445337385406468\n",
            "Backpropagated expectation value with truncation: 0.934050286970965\n"
          ]
        }
      ],
      "source": [
        "print(f\"Expectation value without backpropagation: {result_no_bp}\")\n",
        "print(f\"Backpropagated expectation value: {result_bp}\")\n",
        "print(f\"Backpropagated expectation value with truncation: {result_bp_trunc}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "37834c72",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "Text(0, 0.5, '$M_Z$')"
            ]
          },
          "execution_count": 17,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/operator-back-propagation/extracted-outputs/37834c72-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Plot the results\n",
        "methods = [\n",
        "    \"No backpropagation\",\n",
        "    \"Backpropagation\",\n",
        "    \"Backpropagation w/ truncation\",\n",
        "]\n",
        "values = [result_no_bp, result_bp, result_bp_trunc]\n",
        "error_bars = [std_no_bp, std_bp, std_bp_trunc]\n",
        "\n",
        "ax = plt.gca()\n",
        "plt.bar(methods, values, color=\"#a56eff\", width=0.4, edgecolor=\"#8a3ffc\")\n",
        "plt.errorbar(methods, values, yerr=error_bars, fmt=\"o\", color=\"r\", capsize=5)\n",
        "plt.axhline(0.89)\n",
        "ax.set_ylim([0.8, 0.98])\n",
        "plt.text(0.25, 0.895, \"Exact result\")\n",
        "ax.set_ylabel(r\"$M_Z$\", fontsize=12)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "75f48e6a-c7e4-46f3-9d39-a7a877427a04",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 次のステップ\n",
        "\n",
        "この作品が興味深かった方は、以下の資料もご参照ください：\n",
        "\n",
        "<Admonition type=\"tip\" title=\"推奨事項\">\n",
        "  * [時間発展回路のための近似量子コンパイル](/docs/tutorials/approximate-quantum-compilation-for-time-evolution)\n",
        "  * [トロッター誤差を低減するための多製品対応式](/docs/tutorials/multi-product-formula)\n",
        "  * [`pauli-prop`](https://github.com/Qiskit/pauli-prop)、パウリ伝播のためのRustで高速化されたパッケージ。OBP、古典的な期待値推定、ノイズを含むシミュレーションに関するチュートリアルが用意されている\n",
        "</Admonition>\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.5,
    "qpuSeconds": 240
  },
  "nbformat": 4,
  "nbformat_minor": 5
}