{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "9857bace",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"カットベルペアを用いた動的回路のベンチマーク\"\n",
        "description: \"カットベルペアプロトコルを用いた動的回路機能のベンチマーク\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore LOCC Ristè bitstr textcoords xytext fontsize Carrera */}\n",
        "\n",
        "<span id=\"benchmark-dynamic-circuits-with-cut-bell-pairs\" />\n",
        "\n",
        "# カットベルペアを用いた動的回路のベンチマーク\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d08a5e51",
      "metadata": {},
      "source": [
        "*使用時間推定値: Heron r2 プロセッサ上で22秒（注: これはあくまで推定値です） 実行時間は異なる場合があります。*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0b9b92c8",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 学習成果\n",
        "\n",
        "このチュートリアルを終えた後、ユーザーは以下の点を理解できるようになります：\n",
        "\n",
        "* 回路の途中での測定と古典的なフィードフォワードを用いて動的な回路を構築し、離れた量子ビット間のエンタングルメントをテレポーテーションする方法；\n",
        "* デバイス全体におけるベル対の忠実度を定量化するための誤差指標の算出方法と解釈；\n",
        "* ベンチマーク結果を用いて、LOCCベースの演算に最適な量子ビットペアを特定する方法。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bff9db42",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 前提条件\n",
        "\n",
        "このチュートリアルを進める前に、以下の内容についてあらかじめ理解しておいていただくことをお勧めします：\n",
        "\n",
        "* ベル状態、量子もつれ、量子ゲートなど、 [量子コンピューティングの基礎概念](/learning/courses/basics-of-quantum-information)；\n",
        "* [動的回路](/docs/guides/classical-feedforward-and-control-flow)に関する知識（ [回路の途中での測定](/docs/guides/execute-dynamic-circuits#mid-circuit-measurements)および古典的なフィードフォワード）；\n",
        "* [Qiskit SDK](/docs/guides) および [IBM Quantum® Compute Service](/docs/guides/compute-services#qiskit-runtime) に関する基礎知識、ならびに [IBM Quantum](/docs/guides/cloud-setup) アカウントへのアクセス権。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "77dca78b",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 背景\n",
        "\n",
        "量子ハードウェアは通常、局所的な相互作用に限定されるが、多くのアルゴリズムでは遠隔の量子ビット、あるいは[別々のプロセッサ上の量子ビットを](#references)絡み合わせる必要がある。 動的回路——すなわち、回路途中での測定とフィードフォワードを備えた回路——は、リアルタイムの古典的通信を用いて非局所的な量子操作を効果的に実現することで、これらの制限を克服する方法を提供する。 この手法では、回路の一部（または1つのQPU）における測定結果が、条件付きで別の部分のゲートをトリガーすることができ、これにより長距離にわたってエンタングルメントをテレポーテーションすることが可能となる。 これは、 **局所操作と古典通信（LOCC）** スキームの基盤を成す。このスキームでは、絡み合った資源状態（ベル対）を消費し、測定結果を古典的に通信することで遠隔の量子ビットを連結する。\n",
        "\n",
        "LOCCの有望な応用例の一つは、 [長距離エンタングルメントチュートリアル](/docs/tutorials/long-range-entanglement)で示されているように、テレポーテーションによる仮想的な長距離CNOTゲートの実現である。 直接的な長距離CNOT（ハードウェアの接続性が許さない可能性がある）の代わりに、ベル対を作成し、テレポーテーションに基づくゲート実装を行う。 ただし、このような操作の忠実度はハードウェアの特性に依存する。 必要な遅延時間（測定結果待ちの間）における量子ビットのデコヒーレンスと古典通信の遅延は、もつれた状態を劣化させる可能性がある。 また、中間回路測定の誤差は、条件付きゲートを通じて回路全体に伝播するため、最終測定の誤差よりも修正が困難である。\n",
        "\n",
        "[参照実験](#references)において、著者らは、デバイスのどの部分がLOCCベースのエンタングルメントに最も適しているかを特定するため、ベル対のフィデリティに関するベンチマークを導入している。 その考え方は、プロセッサ内の4つの量子ビットが接続された各グループに対して、小さな動的回路を実行するというものです。 この4量子ビット回路は、まず中央の2つの量子ビットでベル対を生成し、それをリソースとして利用して、LOCCを用いて両端の2つの量子ビットをエンタングルメントさせる。 具体的には、量子ビット1と2を、ローカルに*生成*された未切断のベル対（テレポーテーションを用いず、ハダマール演算とCNOT演算によって直接生成されたベル対）として準備し、その後、テレポーテーション処理によってそのベル対を消費し、量子ビット0と3をエンタングルメントさせる。 回路の実行中に量子ビット1と2の測定が行われ、その結果に基づいて、パウリ補正（量子ビット3にX、量子ビット0にZ）が適用される。 こうして、回路の終了時点で量子ビット0と3はベル状態のまま残される。\n",
        "\n",
        "この最終的なもつれた対の質を定量化するため、その安定化子（具体的には、基底 におけるパリティ（ $Z_0Z_3$ ）と $X$ $Z$ 基底 におけるパリティ（ $X_0X_3$ ））を測定する。完全なベル対の場合、これらの期待値はいずれも +1 に等しい。 実際には、ハードウェアノイズによりこれらの値は低下します。 したがって、各量子ビットペアに対して回路を2回繰り返す：1つの回路は量子ビット0と3を $Z$ 基底で測定し、別の回路は基底 $X$ で測定する。 結果から、その量子ビットのペアに対する $\\langle X_0X_3\\rangle$ と $\\langle Z_0Z_3\\rangle$ の推定値が得られる。 これらの安定化器の理想値(1)に対する二乗誤差の平均(MSE)を、エンタングルメント忠実度の単純な指標として用いる。 MSEが低いほど、2つの量子ビットが理想的なベル状態に近づいた（忠実度が高い）ことを意味し、MSEが高いほど誤りが多いことを示す。 この実験をデバイス全体でスキャンすることで、異なる量子ビット群の測定・フィードフォワード能力をベンチマークし、LOCC演算に最適な量子ビットペアを特定できる。\n",
        "\n",
        "このチュートリアルでは、 IBM Quantum® デバイスを用いた実験を紹介し、動的回路を用いて離れた位置にある量子ビット間のエンタングルメントを生成・評価する方法を解説します。 このデバイス上のすべての4量子ビット線形鎖をマッピングし、それぞれでテレポーテーション回路を実行した後、MSE値の分布を可視化します。 このエンドツーエンドの手順では、 IBM Quantum のCompute Serviceおよび動的回路機能を活用し、モジュール式システムにおいて回路を分割したり、量子アルゴリズムを分散させたりする際に、ハードウェアの特性を考慮した適切な選択を行う方法を示します。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "152c479f",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 要件\n",
        "\n",
        "このチュートリアルを始める前に、以下のものがインストールされていることを確認してください：\n",
        "\n",
        "* Qiskit SDK v2.0 またはそれ以降、 [可視化](/docs/api/qiskit/visualization)サポート付き\n",
        "* v0.40Qiskit Runtime またはそれ以降 (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer v0.17 以降 (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e67f9466",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## セットアップ\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b59e4534",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "\n",
        "def create_bell_stab(initial_layouts):\n",
        "    \"\"\"\n",
        "    Create a circuit for a 1D chain of qubits (number\n",
        "    of qubits must be a multiple of 4), where a middle\n",
        "    Bell pair is consumed to create a Bell at the edge.\n",
        "    Takes as input a list of lists, where each element\n",
        "    of the list is a 1D chain of physical qubits that is\n",
        "    used as the initial_layout for the transpiled circuit.\n",
        "    Returns a list of length-2 tuples, each tuple\n",
        "    contains a circuit to measure the ZZ stabilizer and\n",
        "    a circuit to measure the XX stabilizer of the edge\n",
        "    Bell state.\n",
        "    \"\"\"\n",
        "    bell_circuits = []\n",
        "    for (\n",
        "        initial_layout\n",
        "    ) in initial_layouts:  # Iterate over chains of physical qubits\n",
        "        assert (\n",
        "            len(initial_layout) % 4 == 0\n",
        "        ), \"The length of the chain must be a multiple of 4, \"\n",
        "        f\"len(inital_layout)={len(initial_layout)}\"\n",
        "        num_pairs = len(initial_layout) // 4\n",
        "\n",
        "        bell_parallel = QuantumCircuit(4 * num_pairs, 4 * num_pairs)\n",
        "\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (c0, c1) = pair_idx * 4, pair_idx * 4 + 3  # edge qubits\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "\n",
        "            bell_parallel.h(q0)\n",
        "            bell_parallel.h(q1)\n",
        "            bell_parallel.cx(q1, q2)\n",
        "            bell_parallel.cx(q0, q1)\n",
        "            bell_parallel.cx(q2, q3)\n",
        "            bell_parallel.h(q2)\n",
        "\n",
        "        # add barrier BEFORE measurements and add id in conditional\n",
        "        bell_parallel.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "\n",
        "            bell_parallel.measure(q1, ca0)\n",
        "            bell_parallel.measure(q2, ca1)\n",
        "        # bell_parallel.barrier() #remove barrier after measurement\n",
        "\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "            with bell_parallel.if_test((ca0, 1)):\n",
        "                bell_parallel.x(q3)\n",
        "            with bell_parallel.if_test((ca1, 1)):\n",
        "                bell_parallel.z(q0)\n",
        "                bell_parallel.id(q0)  # add id here for correct alignment\n",
        "\n",
        "        bell_zz = bell_parallel.copy()\n",
        "        bell_zz.barrier()\n",
        "        bell_xx = bell_parallel.copy()\n",
        "        bell_xx.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            bell_xx.h(q0)\n",
        "            bell_xx.h(q3)\n",
        "        bell_xx.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (c0, c1) = pair_idx * 4, pair_idx * 4 + 3  # edge qubits\n",
        "\n",
        "            bell_zz.measure(q0, c0)\n",
        "            bell_zz.measure(q3, c1)\n",
        "\n",
        "            bell_xx.measure(q0, c0)\n",
        "            bell_xx.measure(q3, c1)\n",
        "\n",
        "        bell_circuits.append(bell_zz)\n",
        "        bell_circuits.append(bell_xx)\n",
        "\n",
        "    return bell_circuits\n",
        "\n",
        "\n",
        "def get_mse(result, initial_layouts):\n",
        "    \"\"\"\n",
        "    given a result object and the initial layouts,\n",
        "    returns a dict of layouts and their mse\n",
        "    \"\"\"\n",
        "    layout_mse = {}\n",
        "    for layout_idx, initial_layout in enumerate(initial_layouts):\n",
        "        layout_mse[tuple(initial_layout)] = {}\n",
        "\n",
        "        num_pairs = len(initial_layout) // 4\n",
        "\n",
        "        counts_zz = result[2 * layout_idx].data.c.get_counts()\n",
        "        total_shots = sum(counts_zz.values())\n",
        "\n",
        "        # Get ZZ expectation value\n",
        "        exp_zz_list = []\n",
        "        for pair_idx in range(num_pairs):\n",
        "            exp_zz = 0\n",
        "            for bitstr, shots in counts_zz.items():\n",
        "                bitstr = bitstr[::-1]  # reverse order to big endian\n",
        "                b1, b0 = (\n",
        "                    bitstr[pair_idx * 4],\n",
        "                    bitstr[pair_idx * 4 + 3],\n",
        "                )  # parse bitstring to get edge measurements for each 4-q chain\n",
        "                z_val0 = 1 if b0 == \"0\" else -1\n",
        "                z_val1 = 1 if b1 == \"0\" else -1\n",
        "                exp_zz += z_val0 * z_val1 * shots\n",
        "            exp_zz /= total_shots\n",
        "            exp_zz_list.append(exp_zz)\n",
        "\n",
        "        counts_xx = result[2 * layout_idx + 1].data.c.get_counts()\n",
        "        total_shots = sum(counts_xx.values())\n",
        "\n",
        "        # Get XX expectation value\n",
        "        exp_xx_list = []\n",
        "        for pair_idx in range(num_pairs):\n",
        "            exp_xx = 0\n",
        "            for bitstr, shots in counts_xx.items():\n",
        "                bitstr = bitstr[::-1]  # reverse order to big endian\n",
        "                b1, b0 = (\n",
        "                    bitstr[pair_idx * 4],\n",
        "                    bitstr[pair_idx * 4 + 3],\n",
        "                )  # parse bitstring to get edge measurements for each 4-q chain\n",
        "                x_val0 = 1 if b0 == \"0\" else -1\n",
        "                x_val1 = 1 if b1 == \"0\" else -1\n",
        "                exp_xx += x_val0 * x_val1 * shots\n",
        "            exp_xx /= total_shots\n",
        "            exp_xx_list.append(exp_xx)\n",
        "\n",
        "        mse_list = [\n",
        "            ((exp_zz - 1) ** 2 + (exp_xx - 1) ** 2) / 2\n",
        "            for exp_zz, exp_xx in zip(exp_zz_list, exp_xx_list)\n",
        "        ]\n",
        "\n",
        "        print(f\"layout {initial_layout}\")\n",
        "        for idx in range(num_pairs):\n",
        "            layout_mse[tuple(initial_layout)][\n",
        "                tuple(initial_layout[4 * idx : 4 * idx + 4])\n",
        "            ] = mse_list[idx]\n",
        "            print(\n",
        "                f\"qubits: {initial_layout[4*idx:4*idx+4]}, mse:, \"\n",
        "                f\"{round(mse_list[idx],4)}\"\n",
        "            )\n",
        "            # print(f'exp_zz: {round(exp_zz_list[idx],4)},\n",
        "            # exp_xx: {round(exp_xx_list[idx],4)}')\n",
        "        print(\" \")\n",
        "    return layout_mse\n",
        "\n",
        "\n",
        "def plot_mse_ecdfs(layouts_mse, combine_layouts=False):\n",
        "    \"\"\"\n",
        "    Plot CDF of MSE data for multiple layouts.\n",
        "    Optionally combine all data in a single CDF\n",
        "    \"\"\"\n",
        "\n",
        "    if not combine_layouts:\n",
        "        for initial_layout, layouts in layouts_mse.items():\n",
        "            sorted_layouts = dict(\n",
        "                sorted(layouts.items(), key=lambda item: item[1])\n",
        "            )  # sort layouts by mse\n",
        "\n",
        "            # get layouts and mses\n",
        "            layout_list = list(sorted_layouts.keys())\n",
        "            mse_list = np.asarray(list(sorted_layouts.values()))\n",
        "\n",
        "            # convert to numpy\n",
        "            x = np.array(mse_list)\n",
        "            y = np.arange(1, len(x) + 1) / len(x)\n",
        "\n",
        "            # Prepend (x[0], 0) to start CDF at zero\n",
        "            x = np.insert(x, 0, x[0])\n",
        "            y = np.insert(y, 0, 0)\n",
        "\n",
        "            # Create the plot\n",
        "            plt.plot(\n",
        "                x,\n",
        "                y,\n",
        "                marker=\"x\",\n",
        "                linestyle=\"-\",\n",
        "                label=f\"qubits: {initial_layout}\",\n",
        "            )\n",
        "\n",
        "            # add qubits labels for the edge pairs\n",
        "            for xi, yi, q in zip(x[1:], y[1:], layout_list):\n",
        "                plt.annotate(\n",
        "                    [q[0], q[3]],\n",
        "                    (xi, yi),\n",
        "                    textcoords=\"offset points\",\n",
        "                    xytext=(5, -10),\n",
        "                    ha=\"left\",\n",
        "                    fontsize=8,\n",
        "                )\n",
        "\n",
        "    elif combine_layouts:\n",
        "        all_layouts = {}\n",
        "        all_initial_layout = []\n",
        "        for (\n",
        "            initial_layout,\n",
        "            layouts,\n",
        "        ) in layouts_mse.items():  # puts together all layout information\n",
        "            all_layouts.update(layouts)\n",
        "            all_initial_layout += initial_layout\n",
        "\n",
        "        sorted_layouts = dict(\n",
        "            sorted(all_layouts.items(), key=lambda item: item[1])\n",
        "        )  # sort layouts by mse\n",
        "\n",
        "        # get layouts and mses\n",
        "        layout_list = list(sorted_layouts.keys())\n",
        "        mse_list = np.asarray(list(sorted_layouts.values()))\n",
        "\n",
        "        # convert to numpy\n",
        "        x = np.array(mse_list)\n",
        "        y = np.arange(1, len(x) + 1) / len(x)\n",
        "\n",
        "        # Prepend (x[0], 0) to start CDF at zero\n",
        "        x = np.insert(x, 0, x[0])\n",
        "        y = np.insert(y, 0, 0)\n",
        "\n",
        "        # Create the plot\n",
        "        plt.plot(\n",
        "            x,\n",
        "            y,\n",
        "            marker=\"x\",\n",
        "            linestyle=\"-\",\n",
        "            label=f\"qubits: {sorted(list(set(all_initial_layout)))}\",\n",
        "        )\n",
        "\n",
        "        # add qubit labels for the edge pairs\n",
        "        for xi, yi, q in zip(x[1:], y[1:], layout_list):\n",
        "            plt.annotate(\n",
        "                [q[0], q[3]],\n",
        "                (xi, yi),\n",
        "                textcoords=\"offset points\",\n",
        "                xytext=(5, -10),\n",
        "                ha=\"left\",\n",
        "                fontsize=8,\n",
        "            )\n",
        "\n",
        "    plt.xscale(\"log\")\n",
        "    plt.xlabel(\"Mean squared error of ⟨ZZ⟩ and ⟨XX⟩\")\n",
        "    plt.ylabel(\"Cumulative distribution function\")\n",
        "    plt.title(\"CDF for different initial layouts\")\n",
        "    plt.grid(alpha=0.3)\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "30e65223",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## 小規模シミュレータの例\n",
        "\n",
        "実際のQPU上で実行する前に、4量子ビットのチェーンを用いたノイズのないシミュレータ上でテストを行い、回路がベル対を生成することを確認する `[0, 1, 2, 3]`。 回路の実行には、バックエンドモード `Sampler``AerSimulator` として IBM Quantum を使用しています。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2a16f346",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### ステップ1：古典的な入力を量子問題にマッピングする\n",
        "\n",
        "まず、デバイスのトポロジーに合わせて調整された、すべての候補となるベル対リンクを評価するための量子回路セットを作成します。 `[0, 1, 2, 3]`まず、4量子ビットのチェーンを用いて、そのような回路を構築することから始めます。\n",
        "\n",
        "この `create_bell_stab()` ルーチンは、各チェーンに対して以下の処理を行います：\n",
        "\n",
        "* 中間ベル対を準備する：量子ビット1にハダマード演算を適用し、量子ビット1から量子ビット2へCNOTを適用する。 これにより量子ビット1と2がもつれ合う（ベル状態 $|\\Phi^+\\rangle = (|00\\rangle + |11\\rangle)/\\sqrt{2}$ を生成する）。\n",
        "* 端の量子ビットを絡み合わせる：量子ビット0から量子ビット1へCNOTを適用し、量子ビット2から量子ビット3へCNOTを適用する。 これにより、当初分離していたペアが連結され、次のステップの後には量子ビット0と3がもつれ合う状態になる。 量子ビット2に対してハダマード変換も適用される（これは、前のCNOTと組み合わせて、量子ビット1と2に対するベル測定の一部を構成する）。 現時点では、量子ビット0と3はまだもつれていませんが、量子ビット1と2はより大きな4量子ビット状態においてそれらともつれています。\n",
        "* 中間回路測定とフィードフォワード：量子ビット1と2（中間量子ビット）は計算基底で測定され、2つの古典ビットを生成する。 それらの測定結果に基づき、条件付き操作を適用する：量子ビット1の測定結果（これをビット $m_{12}$ と呼ぶ）が1の場合、量子ビット3にX $X$ ゲートを適用する；量子ビット2の測定結果（ $m_{21}$ ）が1の場合、量子ビット0にY $Z$ ゲートを適用する。 これらの条件付きゲート（Qiskit `if_test`/`if_else` construct を使用して実現）は、標準的なテレポーテーション補正を実装する。 量子ビット1と2の投影によって生じるランダムなパウリの反転を「元に戻す」ことで、量子ビット0と3が測定結果にかかわらず既知のベル状態に収束することを保証する。 このステップの後、量子ビット0と3は理想的にはベル状態に絡み合 $|\\Phi^+\\rangle$ っているべきである。\n",
        "* ベルペアスタビライザーの測定：その後、回路を2つのバージョンに分割します。 最初のバージョンでは、量子ビット0と3の $ZZ$ スタビライザーを測定します。 第二のバージョンでは、これらの量子ビットに対して $XX$ スタビライザーを測定する。\n",
        "\n",
        "4量子ビットの初期配置ごとに、上記の関数は2つの回路を返します（1つは $ZZ$ 用、もう1つは $XX$ のスタビライザー測定用です）。 これらの回路には、回路中間での測定や条件分岐（if/else）演算が含まれており、これらは動的回路の主要な命令である。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "160e25c5",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/edc-cut-bell-pair-benchmarking/extracted-outputs/160e25c5-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 19,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "# 4-qubit chain for simulation\n",
        "sim_layout = [[0, 1, 2, 3]]\n",
        "\n",
        "aer_backend = AerSimulator()\n",
        "sim_circuits = create_bell_stab(sim_layout)\n",
        "sim_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "17953786",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### ステップ2：量子ハードウェア実行に向けた問題の最適化\n",
        "\n",
        "回路を実行する前に、指定されたバックエンドでサポートされているゲート演算に変換する必要があります。 トランスパイレーションにより、抽象回路が選択されたバックエンドの物理量子ビットおよびゲートセットにマッピングされます。 各チェーンごとに具体的な物理量子ビットを（回路生成器に を `initial_layout` 指定することで）すでに選択しているため、その固定レイアウトを持つトランスパイラ `optimization_level=0` を使用します。 これにより、Qiskitに対して、量子ビットの再割り当てを行わないよう、また回路構造を変更する可能性のある大規模な最適化を実行しないよう指示します。 操作の順序（特に条件ゲート）を、指定どおりに厳密に維持したいと考えています。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "61dd47b7",
      "metadata": {},
      "outputs": [],
      "source": [
        "pm_sim = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=aer_backend, initial_layout=sim_layout[0]\n",
        ")\n",
        "isa_sim_circuits = pm_sim.run(sim_circuits)\n",
        "isa_sim_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cd161710",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### ステップ3: `Qiskit primitives`を使用して実行する\n",
        "\n",
        "これで、ノイズのないシミュレータバックエンド上で実験を実行できるようになりました。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "71750073",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "layout [0, 1, 2, 3]\n",
            "qubits: [0, 1, 2, 3], mse:, 0.0\n",
            " \n"
          ]
        }
      ],
      "source": [
        "# Run on noiseless simulator\n",
        "sampler_sim = Sampler(mode=aer_backend)\n",
        "sim_job = sampler_sim.run(isa_sim_circuits)\n",
        "sim_mse = get_mse(sim_job.result(), sim_layout)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4b0b451f",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-the-desired-classical-format\" />\n",
        "\n",
        "### ステップ4：後処理を行い、結果を望ましい古典的な形式で返す\n",
        "\n",
        "最終ステップとして、テスト対象の各量子ビットグループについて二乗誤差平均（MSE）を計算し、結果をまとめる。 各チェーンについて、測定された $\\langle Z_0Z_3\\rangle$ と が $\\langle X_0X_3\\rangle$ 得られた。量子ビット0と3が $|\\Phi^+\\rangle$ 完全なベル状態に絡み合っていた場合、これら両方が+1となることが予想される。 誤差をMSEを用いて定量化する：\n",
        "\n",
        "$\\text{MSE} = \\frac{( \\langle Z_0Z_3\\rangle - 1)^2 + (\\langle X_0X_3\\rangle - 1)^2}{2}.$\n",
        "\n",
        "完全なベル対の場合、この値は0となりますが、量子もつれ状態のノイズが増加するにつれて値は大きくなります（ランダムな結果の期待値が0付近にある場合、MSEは1に近づきます）。 このコードは、4量子ビットのグループごとにこのMSEを計算します。 ノイズがないため、この小規模なシミュレータの例では、予想通りMSEが0となることがわかります。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c8e42569",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## 大規模なハードウェアの例\n",
        "\n",
        "ここで、これらすべての詳細を統合し、より大規模な単一のワークフローとしてまとめ、それを実際の量子ハードウェア上で実行します。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "55f49481",
      "metadata": {},
      "outputs": [],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(operational=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "27ee6db2",
      "metadata": {},
      "source": [
        "デバイス結合マップをプログラムで探索し、4つの量子ビットからなるすべての直列接続チェーンを抽出します。 このような各連鎖（量子ビットの添字 $[q0-q1-q2-q3]$ で表される）は、エンタングルメント交換回路のテストケースとして機能する。 length-4 のすべての可能な経路を特定することで、プロトコルを実現し得る量子ビットのグループ分けについて、最大限の網羅性を確保します。\n",
        "\n",
        "これらのチェーンは、デバイスグラフに対して貪欲探索を行うヘルパー関数を使用して生成します。 4つの4量子ビット鎖を束ねて16量子ビットのグループにした「ストリップ」を返す。 バンドリングにより、チップの異なる領域で複数の4量子ビット実験を並行して実行し、デバイス全体を効率的に活用することが可能になります。 各16キュービットのストライプには4つの互いに交わらないチェーンが含まれており、つまりそのグループ内ではどのキュービットも重複して使用されない。 たとえば、1つのストライプは、 $[0-1-2-3]$、 $[4-5-6-7]$、 $[8-9-10-11]$、 $[12-13-14-15]$ の各チェーンをまとめてパッケージ化したもので構成される場合があります。 `leftover` ストライプに含まれていなかったクビットは、変数に返されます。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0cae5453",
      "metadata": {},
      "outputs": [],
      "source": [
        "from itertools import chain\n",
        "from collections import defaultdict\n",
        "\n",
        "\n",
        "def stripes16_from_backend(backend):\n",
        "    \"\"\"\n",
        "    Creates stripes of 16 qubits, four non-overlapping\n",
        "    four-qubit chains, that cover as much of the coupling\n",
        "    map as possible. Returns any unused qubits as leftovers.\n",
        "    \"\"\"\n",
        "    # get the undirected adjacency list\n",
        "    edges = backend.coupling_map.get_edges()\n",
        "    graph = defaultdict(set)\n",
        "    for u, v in edges:\n",
        "        graph[u].add(v)\n",
        "        graph[v].add(u)\n",
        "\n",
        "    qubits = sorted(graph)  # all qubit indices that appear\n",
        "\n",
        "    # greedy search for 4-long linear chains (blocks) ────────────\n",
        "    used = set()  # qubits already placed in a block\n",
        "    blocks = []  # each block is a four-qubit list\n",
        "\n",
        "    for q in qubits:  # deterministic order for reproducibility\n",
        "        if q in used:\n",
        "            continue  # already consumed by earlier block\n",
        "\n",
        "        # depth-first \"straight\" walk of length 3 without revisiting nodes\n",
        "        def extend(path):\n",
        "            if len(path) == 4:\n",
        "                return path\n",
        "            tip = path[-1]\n",
        "            for nbr in sorted(graph[tip]):  # deterministic\n",
        "                if nbr not in path and nbr not in used:\n",
        "                    maybe = extend(path + [nbr])\n",
        "                    if maybe:\n",
        "                        return maybe\n",
        "            return None\n",
        "\n",
        "        block = extend([q])\n",
        "        if block:  # found a 4-node path\n",
        "            blocks.append(block)\n",
        "            used.update(block)\n",
        "\n",
        "    # bundle four four-qubit blocks into one 16-qubit\n",
        "    # stripe (max number of measurement compatible with if-else)\n",
        "    stripes = [\n",
        "        list(chain.from_iterable(blocks[i : i + 4]))\n",
        "        for i in range(0, len(blocks) // 4 * 4, 4)  # full groups of four\n",
        "    ]\n",
        "\n",
        "    leftovers = set(qubits) - set(chain.from_iterable(stripes))\n",
        "    return stripes, leftovers"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "3cf9db43",
      "metadata": {},
      "outputs": [],
      "source": [
        "initial_layouts, leftover = stripes16_from_backend(backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8121aa97",
      "metadata": {},
      "source": [
        "`create_bell_stab()`次に、関数 を用いて、各16量子ビットのストライプに対応する回路を構築する。 このステップの終了時点で、デバイス上のすべての4量子ビットチェーンを網羅する回路のリストが得られます。 その後、回路をトランスパイルして実際のバックエンド上で実行し、結果に対して後処理を行います。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "bd04755f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "layout [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n",
            "qubits: [0, 1, 2, 3], mse:, 0.6302\n",
            "qubits: [4, 5, 6, 7], mse:, 0.0949\n",
            "qubits: [8, 9, 10, 11], mse:, 0.1729\n",
            "qubits: [12, 13, 14, 15], mse:, 0.0473\n",
            " \n",
            "layout [16, 23, 22, 21, 17, 27, 26, 25, 18, 31, 30, 29, 19, 35, 34, 33]\n",
            "qubits: [16, 23, 22, 21], mse:, 0.0533\n",
            "qubits: [17, 27, 26, 25], mse:, 0.2966\n",
            "qubits: [18, 31, 30, 29], mse:, 0.0447\n",
            "qubits: [19, 35, 34, 33], mse:, 0.0392\n",
            " \n",
            "layout [36, 41, 42, 43, 37, 45, 46, 47, 38, 49, 50, 51, 39, 53, 54, 55]\n",
            "qubits: [36, 41, 42, 43], mse:, 0.1577\n",
            "qubits: [37, 45, 46, 47], mse:, 0.0705\n",
            "qubits: [38, 49, 50, 51], mse:, 0.2914\n",
            "qubits: [39, 53, 54, 55], mse:, 0.1711\n",
            " \n",
            "layout [56, 63, 62, 61, 57, 67, 66, 65, 58, 71, 70, 69, 59, 75, 74, 73]\n",
            "qubits: [56, 63, 62, 61], mse:, 0.1236\n",
            "qubits: [57, 67, 66, 65], mse:, 0.9969\n",
            "qubits: [58, 71, 70, 69], mse:, 0.0631\n",
            "qubits: [59, 75, 74, 73], mse:, 0.0301\n",
            " \n",
            "layout [76, 81, 82, 83, 77, 85, 86, 87, 78, 89, 90, 91, 79, 93, 94, 95]\n",
            "qubits: [76, 81, 82, 83], mse:, 0.2787\n",
            "qubits: [77, 85, 86, 87], mse:, 0.0497\n",
            "qubits: [78, 89, 90, 91], mse:, 0.1271\n",
            "qubits: [79, 93, 94, 95], mse:, 0.0468\n",
            " \n",
            "layout [96, 103, 102, 101, 97, 107, 106, 105, 98, 111, 110, 109, 99, 115, 114, 113]\n",
            "qubits: [96, 103, 102, 101], mse:, 0.8657\n",
            "qubits: [97, 107, 106, 105], mse:, 0.0399\n",
            "qubits: [98, 111, 110, 109], mse:, 0.0667\n",
            "qubits: [99, 115, 114, 113], mse:, 0.2444\n",
            " \n",
            "layout [116, 121, 122, 123, 117, 125, 126, 127, 118, 129, 130, 131, 119, 133, 134, 135]\n",
            "qubits: [116, 121, 122, 123], mse:, 0.0429\n",
            "qubits: [117, 125, 126, 127], mse:, 0.0487\n",
            "qubits: [118, 129, 130, 131], mse:, 0.0823\n",
            "qubits: [119, 133, 134, 135], mse:, 0.0583\n",
            " \n",
            "layout [136, 143, 142, 141, 137, 147, 146, 145, 138, 151, 150, 149, 139, 155, 154, 153]\n",
            "qubits: [136, 143, 142, 141], mse:, 0.0209\n",
            "qubits: [137, 147, 146, 145], mse:, 0.0384\n",
            "qubits: [138, 151, 150, 149], mse:, 0.4941\n",
            "qubits: [139, 155, 154, 153], mse:, 0.1062\n",
            " \n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "circuits = create_bell_stab(initial_layouts)\n",
        "\n",
        "# -------------------------Step 2-------------------------\n",
        "isa_circuits = []\n",
        "for ind, init_layout in enumerate(initial_layouts):\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=0, backend=backend, initial_layout=init_layout\n",
        "    )\n",
        "    isa_circ = pm.run(circuits[ind * 2 : ind * 2 + 2])\n",
        "    isa_circuits.extend(isa_circ)\n",
        "isa_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)\n",
        "\n",
        "# -------------------------Step 3-------------------------\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.environment.job_tags = [\"TUT_BDC\"]\n",
        "job = sampler.run(isa_circuits)\n",
        "\n",
        "# -------------------------Step 4-------------------------\n",
        "layouts_mse = get_mse(job.result(), initial_layouts)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "87484abf",
      "metadata": {},
      "source": [
        "結果から、デバイス全体でエンタングルメントの品質に幅広いばらつきが明らかになった。 この結果は、使用する物理量子ビットによってベル状態の忠実度が1桁以上変動し得るという本論文の知見を裏付けるものである。 実用的な観点から言えば、これはチップ内の特定の領域や接続が、他の領域や接続よりも回路中間測定やフィードフォワード演算をはるかに効率的に実行できることを意味する。 量子ビットの読み出し誤差、量子ビットの寿命、クロストークなどの要因が、これらの差異に寄与している可能性が高い。 例えば、あるチェーンに特にノイズの多い読み出し量子ビットが含まれている場合、回路中盤での測定が信頼性を欠く可能性があり、その結果、そのエンタングルメントペアの忠実度が低下する（MSEが高くなる）。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5af0facd",
      "metadata": {},
      "source": [
        "最後に、すべてのチェーンのMSE値の累積分布関数（CDF）をプロットすることで、全体的な性能を可視化する。 CDFプロットは、x軸にMSE閾値、y軸にそのMSE以下となる量子ビットペアの割合を示しています。 この曲線はゼロから始まり、閾値がすべてのデータポイントを包含するように大きくなるにつれて1に近づく。 低いMSE付近での急な上昇は、多くのペアが高精度であることを示唆する。緩やかな上昇は、多くのペアに大きな誤差があることを意味する。 CDFに最適なペアの識別情報を注釈として付与する。 プロットでは、累積分布関数（CDF）上の各点が1つの4量子ビット鎖の二乗誤差（MSE）に対応し、その実験でエンタングルメントが生じた量子ビットのペアの $[q0, q3]$ インデックスで点をラベル付けする。 これにより、どの物理量子ビットペアが最高のパフォーマンスを発揮しているか（CDFの左端の点）を容易に特定できます。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "678ddac9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/edc-cut-bell-pair-benchmarking/extracted-outputs/678ddac9-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "plot_mse_ecdfs(layouts_mse, combine_layouts=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba738d2b",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 次のステップ\n",
        "\n",
        "<Admonition type=\"tip\" title=\"推奨事項\">\n",
        "  この作品に興味を持たれた方は、以下の資料もご参照ください：\n",
        "\n",
        "  * [動的回路を用いた長距離エンタングルメント](/docs/tutorials/long-range-entanglement)の実装方法を学ぶ\n",
        "  * [動的回路を用いて、キック付きアイジング・ハミルトニアン](/docs/tutorials/dc-hex-ising)をシミュレートする方法を学びましょう\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "109cd9c3",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## 参照\n",
        "\n",
        "[\\[1\\] カレラ・バスケス, A., トルノー, C., Ristè, D. ほか 量子プロセッサとリアルタイムの古典的通信を組み合わせる。 『Nature』636号、75-79頁（2024年）](https://www.nature.com/articles/s41586-024-08178-2)。\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"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "state": {},
        "version_major": 2,
        "version_minor": 0
      }
    },
    "hours": 1,
    "qpuSeconds": 22
  },
  "nbformat": 4,
  "nbformat_minor": 5
}