{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"時空間符号を用いた低オーバーヘッドのエラー検出\"\n",
        "description: \"qiskit-paulice を使用して時空パウリチェックを自動的に挿入し、チェックシンドロームによるポストセレクションによって、サンプリングされたクリフォード回路のフィデリティを向上させる。\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore postsel, frameon, virt */}\n",
        "\n",
        "<span id=\"low-overhead-error-detection-with-spacetime-codes\" />\n",
        "\n",
        "# 時空間符号を用いた低オーバーヘッドのエラー検出\n",
        "\n",
        "*所要時間の目安：Heronプロセッサ（`ibm_kingston` または同等のもの）で4分（注：これはあくまで目安です。 （実行時間は環境によって異なる場合があります。）*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "learning-outcomes",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 学習成果\n",
        "\n",
        "このチュートリアルを修了すると、以下の内容を理解できるようになります：\n",
        "\n",
        "* 時空パウリチェックがクリフォード回路の論理エラーをどのように検出するか、また、そのシンドロームを用いたポストセレクションが、サンプリングされた分布の忠実度をどのように向上させるか。\n",
        "* パッケージ `qiskit-paulice` を使用して、 `get_check_qubits`、 `NoiseModel`、およびを用いて、ハードウェア効率の高いチェックを自動的に検出して挿入する方法 `add_pauli_checks`。\n",
        "* 安定化状態の安定化子状態をサンプリングし、チェックシンドロームを用いてポストセレクションを行うことで、その忠実度を推定する方法。\n",
        "* IBM Quantum® ハードウェア上でエラー検出ワークフロー全体を実行し、ノイズを含むフィデリティとポストセレクション後のフィデリティを比較する方法。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "prerequisites",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 前提条件\n",
        "\n",
        "以下のトピックについて、あらかじめ理解しておくことをお勧めします：\n",
        "\n",
        "* ユーティリティ規模の量子コンピューティングにおける[ハードウェアの基礎](/learning/courses/utility-scale-quantum-computing/hardware)。\n",
        "* クリフォード形式とスタビライザー形式。これには、スタビライザー群が純粋なスタビライザー状態をどのように記述するかも含まれる。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 背景\n",
        "\n",
        "Simon Martiel および Ali Javadi-Abhari による「 [時空間符号を用いた低オーバーヘッドのエラー検出](https://arxiv.org/abs/2504.15725)」[ \\[1\\] では、](#references) 完全なエラー訂正と軽量なエラー軽減の中間に位置する、クリフォード支配回路における論理エラーを検出する手法が提案されている。 このアイデアは、van den Bergらによる「 [コヒーレント・パウリ・チェックによるシングルショット誤差の低減](https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.5.033193)」[ \\[2\\]](#references) で提案されたコヒーレント・パウリ・チェック（CPC）に基づいている。 どちらのアプローチにおいても、クリフォード「ペイロード」回路がアンシラ量子ビットと量子もつれ状態を形成し、特定の不変量を検証する。 アンシラの測定により、実行中にエラーが検出されたかどうかを示すシンドロームが生成されます。 エラーが検出されなかったサンプルのみを保持することで、サンプリングされた分布の忠実度は向上するが、その代償としてポストセレクション率は低下する。\n",
        "\n",
        "コヒーレント・パウリチェックと時空間チェックの主な違いは、測定の対象となる演算子にある。 コヒーレント・パウリチェックは、時間局在的で重みが大きい演算子を測定する。 「ヘビーヘックス」のような接続性が制限された量子ビットトポロジーでは、こうしたチェックには多くのSWAPゲートが必要となり、その結果、回路が深くなりすぎて、実際には実行できなくなることがよくあります。 その代わりに、チェックを時空間コードとして実装することで、各チェックがペイロード回路全体に空間的・時間的に分散されることになる。 これにより、量子ビットや処理深度のオーバーヘッドを低く抑えつつ、論理エラーの検出において高い効果を維持できる、ハードウェア効率に優れた符号化が実現される。\n",
        "\n",
        "<span id=\"what-the-qiskit-paulice-package-does\" />\n",
        "\n",
        "### このパッケージ `qiskit-paulice` の機能\n",
        "\n",
        "このパッケージ `qiskit-paulice` は、これらのチェックの構築を自動化するため、手作業で作成する必要がなくなります。 その主な役割は、回路内の各箇所において、エラー検出率を最大化し、かつ量子ビットのオーバーヘッドを最小化するような、有効な時空間パウリチェックを見つけ出し、挿入することです。 チェックは、その演算子がペイロード回路の論理動作を変更しない場合に*有効*であり、エンタングルメントゲートの使用数が*少ない場合は低負荷であり*、チェック自体がもたらすノイズに比べてエラーの大部分を検出できる場合は*効果的*である。 このパッケージは、ノイズモデルに基づいて候補チェックを評価し、最適なものを回路に組み込みます。 このチュートリアルでは、次の3つのAPIメソッドを使用します：\n",
        "\n",
        "* `get_check_qubits` バックエンドの結合マップを検査し、ターゲット量子ビットとアンシラ量子ビットのペアを返します。 の確認には を使用 `target_qubits[i]` します `ancilla_qubits[i]`。\n",
        "* `NoiseModel.from_backend` バックエンドのベンチマークデータから、大まかなノイズモデルを構築します。 このモデルは候補のチェックを評価するため、学習済みの正確なノイズモデルは必要ありません。 学習済みパウリ・リンドブラッド・モデルについては、を参照のこと [`NoiseModel.from_pauli_lindblad_maps`](/docs/api/qiskit-addon-paulice/qiskit-paulice-noise-models#from_pauli_lindblad_maps)。\n",
        "* `add_pauli_checks` 回路内のチェック箇所を検出し、チェックを挿入します。 これは、チェックの数が増加していく `CheckedCircuit` オブジェクトのシーケンスを返し、各オブジェクトは、測定されたビット列をシンドロームベクトルに写像する `get_postselection_method` 関数を提供します。 この引 `cost` 数は、チェックのスコアを算出する関数（`gamma`、事後選択された逆ノイズチャネルのサンプリングオーバーヘッド、または `LER`、論理誤り率）を選択します。 この引数 `method` により、検索戦略（`windowed`、 `genetic`、または `windowed_genetic`）が選択されます。 このチュートリアルでは と `cost=\"gamma\"` を使用しており `method=\"windowed\"`、これらを組み合わせることで、決定論的かつ再現性のあるチェックの選択が可能になります。\n",
        "\n",
        "<span id=\"estimate-fidelity-from-stabilizer-sampling\" />\n",
        "\n",
        "### スタビライザーサンプリングから忠実度を推定する\n",
        "\n",
        "エラー検出がどの程度機能しているかを測定するには、回路が理想的に生成するスタビライザー状態 $|\\psi\\rangle = U|0\\rangle^{\\otimes n}$ と、ハードウェアが実際に出力するノイズの混入した状態 $\\rho$ とを比較して、その忠実度を推定することができます。 純粋な安定子状態 $|\\psi\\rangle$ への射影は、その安定子群 $\\mathcal{S}$ の $2^n$ 個の要素に対する一様平均に等しい：\n",
        "\n",
        "$|\\psi\\rangle\\langle\\psi| = \\frac{1}{2^n}\\sum_{G \\in \\mathcal{S}} G.$\n",
        "\n",
        "これをフィデリティに代入すると、フィデリティは $\\rho$ となり、これは $\\rho$ に関するすべての安定化子 $G \\in \\mathcal{S}$ の平均期待値として表される：\n",
        "\n",
        "$F = \\mathrm{Tr}(\\rho|\\psi\\rangle\\langle\\psi|) = \\frac{1}{2^n} \\sum_{G \\in \\mathcal{S}} \\mathrm{Tr}(\\rho G) = \\frac{1}{2^n} \\sum_{G \\in \\mathcal{S}} \\langle G \\rangle_\\rho.$\n",
        "\n",
        "より大規模な問題の場合、 $2^n$ のすべての安定化子列を列挙することは現実的ではないため、ランダムな標本からフィデリティを推定することができます。 $\\mathcal{S}$ から $M$ の安定化因子 $G_1, \\ldots, G_M$ を均一にランダムに抽出すると、不偏推定量が得られる：\n",
        "\n",
        "$\\hat F_M = \\frac{1}{M} \\sum_{i=1}^M \\langle G_i \\rangle_\\rho.$\n",
        "\n",
        "クリフォード回路はスタビライザー状態を生成するため、そのスタビライザーのサンプリングされた期待値から、そのフィデリティを直接推定することができます。 このチュートリアルでは、まずシミュレータ上で簡単な回路を用いてワークフローの手順を解説し、その後、より大規模で複雑な回路を用いてハードウェア上で同じワークフローを実行します。 回路にクリフォード演算以外の演算が増えるにつれて、有効なチェックの数は急速に減少するため、この手法はクリフォード演算が支配的な回路において最も効果を発揮する。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 要件\n",
        "\n",
        "このチュートリアルを始める前に、以下のものがインストールされていることを確認してください：\n",
        "\n",
        "* Qiskit SDK v2.0 またはそれ以降、 [可視化](/docs/api/qiskit/visualization)機能をサポートしたもの\n",
        "* Qiskit Runtime v0.40 またはそれ以降 (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer v0.17 以降 (`pip install qiskit-aer`)\n",
        "* Qiskit Paulice (`pip install qiskit-paulice`)\n",
        "* tqdm (`pip install tqdm`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## セットアップ\n",
        "\n",
        "必要なライブラリをインポートし、インポートでは利用できないヘルパー関数を定義します。 この関 `random_clifford_circuit` 数は、ブリックワーク方式によるランダムなクリフォードペイロードを構築し、バックエンドのカップリングマップを検索 `find_check_layout` して、利用可能なアンシラが多数あり、誤差の少ない量子ビット経路を見つけ出し、出 `NoiseLearner` 力をノイズ `qiskit-paulice` モデルに変換 `learned_noise_model` し、スタビライザーが計算基底で測定されるように回路を回転 `append_basis_rotation` させ、サンプリングされたカウント数からスタビライザーの期待値を計算 `expectation` し、実行中のフィデリティ推定値を追跡 `cum_mean_sem` します。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "setup-imports",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Standard library imports\n",
        "import random\n",
        "import time\n",
        "\n",
        "# External libraries\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "from tqdm import tqdm\n",
        "\n",
        "# Qiskit\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import Clifford, Pauli, PauliLindbladMap, PauliList\n",
        "from qiskit.result import sampled_expectation_value\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit.visualization import plot_coupling_map\n",
        "\n",
        "# Qiskit Aer\n",
        "from qiskit_aer import AerSimulator\n",
        "from qiskit_aer.noise import NoiseModel as AerNoiseModel\n",
        "from qiskit_aer.noise import ReadoutError, depolarizing_error\n",
        "\n",
        "# Qiskit IBM Runtime\n",
        "from qiskit_ibm_runtime import NoiseLearner, QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "\n",
        "# Qiskit Paulice\n",
        "from qiskit_paulice import add_pauli_checks\n",
        "from qiskit_paulice.layout import get_check_qubits\n",
        "from qiskit_paulice.noise_models import NoiseModel"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "setup-helpers",
      "metadata": {},
      "outputs": [],
      "source": [
        "def random_clifford_circuit(\n",
        "    num_qubits: int, depth: int, rng: np.random.Generator\n",
        ") -> QuantumCircuit:\n",
        "    \"\"\"Brickwork random Clifford on `num_qubits`, with `depth` CZ layers.\"\"\"\n",
        "    qc = QuantumCircuit(num_qubits)\n",
        "    qc.h(range(num_qubits))\n",
        "    for d in range(depth):\n",
        "        for i in range(d % 2, num_qubits - 1, 2):\n",
        "            qc.cz(i, i + 1)\n",
        "        for q in range(num_qubits):\n",
        "            if rng.integers(0, 2):\n",
        "                qc.sx(q)\n",
        "            if rng.integers(0, 2):\n",
        "                qc.s(q)\n",
        "            if rng.integers(0, 2):\n",
        "                qc.sx(q)\n",
        "    return qc\n",
        "\n",
        "\n",
        "def find_check_layout(\n",
        "    backend,\n",
        "    num_qubits: int,\n",
        "    rng: np.random.Generator,\n",
        "    num_trials: int = 200,\n",
        "    max_gate_error: float = 0.03,\n",
        "    max_readout_error: float = 0.2,\n",
        ") -> list[int]:\n",
        "    \"\"\"Find a low-error path of `num_qubits` qubits with many available ancillas.\n",
        "\n",
        "    Builds random self-avoiding walks on the coupling map, excluding the qubits\n",
        "    and two-qubit gates whose reported errors exceed the thresholds, and keeps\n",
        "    the path that offers the most target and ancilla pairs. Ties are broken by\n",
        "    the lower average two-qubit gate error along the path.\n",
        "    \"\"\"\n",
        "    target = backend.target\n",
        "    gate_2q = next(\n",
        "        name for name in (\"cz\", \"ecr\", \"cx\") if name in target.operation_names\n",
        "    )\n",
        "\n",
        "    # Collect per-edge gate errors and per-qubit readout errors\n",
        "    edge_error = {}\n",
        "    for qubits, props in target[gate_2q].items():\n",
        "        edge = tuple(sorted(qubits))\n",
        "        if props is not None and props.error is not None:\n",
        "            edge_error[edge] = min(edge_error.get(edge, 1.0), props.error)\n",
        "    readout_error = {\n",
        "        qubit: target[\"measure\"][(qubit,)].error\n",
        "        for (qubit,) in target[\"measure\"]\n",
        "    }\n",
        "\n",
        "    # Keep only the edges whose gate and readout errors are acceptable\n",
        "    adjacency = {}\n",
        "    for (q1, q2), error in edge_error.items():\n",
        "        if (\n",
        "            error <= max_gate_error\n",
        "            and readout_error.get(q1, 1.0) <= max_readout_error\n",
        "            and readout_error.get(q2, 1.0) <= max_readout_error\n",
        "        ):\n",
        "            adjacency.setdefault(q1, set()).add(q2)\n",
        "            adjacency.setdefault(q2, set()).add(q1)\n",
        "\n",
        "    # Random self-avoiding walks; keep the path with the most check pairs\n",
        "    starts = sorted(adjacency)\n",
        "    best_path = None\n",
        "    best_score = (-1, float(\"inf\"))\n",
        "    for _ in range(num_trials):\n",
        "        path = [starts[rng.integers(len(starts))]]\n",
        "        while len(path) < num_qubits:\n",
        "            options = sorted(adjacency[path[-1]] - set(path))\n",
        "            if not options:\n",
        "                break\n",
        "            path.append(options[rng.integers(len(options))])\n",
        "        if len(path) < num_qubits:\n",
        "            continue\n",
        "        num_pairs = len(get_check_qubits(backend.coupling_map, path)[0])\n",
        "        mean_error = float(\n",
        "            np.mean(\n",
        "                [edge_error[tuple(sorted(e))] for e in zip(path, path[1:])]\n",
        "            )\n",
        "        )\n",
        "        if num_pairs > best_score[0] or (\n",
        "            num_pairs == best_score[0] and mean_error < best_score[1]\n",
        "        ):\n",
        "            best_path, best_score = path, (num_pairs, mean_error)\n",
        "\n",
        "    if best_path is None:\n",
        "        raise RuntimeError(\n",
        "            \"No connected low-error path found. Relax the error thresholds.\"\n",
        "        )\n",
        "    return best_path\n",
        "\n",
        "\n",
        "def learned_noise_model(layer_errors, layout: list[int]) -> NoiseModel:\n",
        "    \"\"\"Build a `NoiseModel` from `NoiseLearner` results.\n",
        "\n",
        "    `NoiseLearner` reports one `PauliLindbladError` per entangling layer, whose\n",
        "    generators are indexed against that layer's own physical qubits, while\n",
        "    `NoiseModel.from_pauli_lindblad_maps` expects `PauliLindbladMap`s indexed the\n",
        "    way `NoiseModel.from_backend` indexes them: by position in `layout`. This\n",
        "    translates between the two and drops generators that fall outside `layout`.\n",
        "    \"\"\"\n",
        "    phys_to_virt = {phys: virt for virt, phys in enumerate(layout)}\n",
        "    maps = []\n",
        "    for layer in layer_errors:\n",
        "        if layer.error is None:\n",
        "            continue\n",
        "        terms = []\n",
        "        for pauli, rate in zip(\n",
        "            layer.error.generators, layer.error.rates, strict=True\n",
        "        ):\n",
        "            label, indices = [], []\n",
        "            for local, phys in enumerate(layer.qubits):\n",
        "                x, z = bool(pauli.x[local]), bool(pauli.z[local])\n",
        "                if not (x or z):\n",
        "                    continue\n",
        "                if phys not in phys_to_virt:\n",
        "                    break  # generator reaches outside the layout, so skip it\n",
        "                label.append(\"Y\" if x and z else \"X\" if x else \"Z\")\n",
        "                indices.append(phys_to_virt[phys])\n",
        "            else:\n",
        "                if label:\n",
        "                    terms.append(\n",
        "                        (\"\".join(label), tuple(indices), float(rate))\n",
        "                    )\n",
        "        # Each map needs a 2-qubit generator to define an entangling layer\n",
        "        if any(len(t[1]) == 2 for t in terms):\n",
        "            maps.append(\n",
        "                PauliLindbladMap.from_sparse_list(\n",
        "                    terms, num_qubits=len(layout)\n",
        "                )\n",
        "            )\n",
        "    if not maps:\n",
        "        raise RuntimeError(\n",
        "            \"No usable layer errors. Check that the learner ran on this layout.\"\n",
        "        )\n",
        "    return NoiseModel.from_pauli_lindblad_maps(maps)\n",
        "\n",
        "\n",
        "def append_basis_rotation(\n",
        "    circuit: QuantumCircuit, pauli: Pauli\n",
        ") -> QuantumCircuit:\n",
        "    \"\"\"Strip measurements, append basis rotations for `pauli`, and re-measure.\"\"\"\n",
        "    out = circuit.remove_final_measurements(inplace=False)\n",
        "    for q in range(pauli.num_qubits):\n",
        "        if pauli.x[q]:\n",
        "            if pauli.z[q]:\n",
        "                out.sdg(q)\n",
        "            out.h(q)\n",
        "    out.measure_all()\n",
        "    return out\n",
        "\n",
        "\n",
        "def expectation(counts: dict, pauli: Pauli) -> float:\n",
        "    \"\"\"Expectation value of `pauli` from counts measured in the Z basis.\n",
        "\n",
        "    Pads with identity on any qubits beyond the support of `pauli`, such as the\n",
        "    check ancillas that appear in the postselected counts.\n",
        "    \"\"\"\n",
        "    if not counts:\n",
        "        return float(\"nan\")\n",
        "    n = pauli.num_qubits\n",
        "    sign = -1 if int(pauli.phase) % 4 == 2 else 1\n",
        "    total = len(next(iter(counts)))\n",
        "    label = \"\".join(\n",
        "        \"Z\" if q < n and (pauli.x[q] or pauli.z[q]) else \"I\"\n",
        "        for q in range(total - 1, -1, -1)\n",
        "    )\n",
        "    return sign * sampled_expectation_value(counts, label)\n",
        "\n",
        "\n",
        "def cum_mean_sem(values: np.ndarray):\n",
        "    \"\"\"Cumulative mean and standard error of the mean, ignoring NaNs.\"\"\"\n",
        "    valid = ~np.isnan(values)\n",
        "    total = np.cumsum(np.where(valid, values, 0.0))\n",
        "    total_sq = np.cumsum(np.where(valid, values**2, 0.0))\n",
        "    count = np.maximum(np.cumsum(valid).astype(float), 1)\n",
        "    mean = total / count\n",
        "    sem = np.sqrt(np.maximum(total_sq / count - mean**2, 0) / count)\n",
        "    return np.where(np.cumsum(valid) > 0, mean, np.nan), sem"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-scale-header",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## 小規模シミュレータの例\n",
        "\n",
        "このセクションでは、ノイズの多いシミュレータを用いたワークフロー全体について解説します。 この手法では、バックエンドのベンチマークデータを用いて量子ビットの配置とノイズモデルを選択し、チェックを自動的に見つけ出し、サンプリングされた分布に対してポストセレクションを適用することで、フィデリティの向上を示す。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### ステップ1：古典的な入力を量子問題に写像する\n",
        "\n",
        "ペイロード回路は、浅い一次元ブリックワーク型ランダム・クリフォード回路である。 この回路はクリフォード回路であるため、サンプリングされたスタビライザーの期待値からそのフィデリティを直接推定できるスタビライザー状態を生成します。 次のステップでチェック箇所が視覚的に把握しやすいよう、まずは単純な回路から始めましょう。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "step1-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step1-code-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "num_qubits = 12\n",
        "depth = 4\n",
        "seed = 1764\n",
        "rng = np.random.default_rng(seed)\n",
        "np.random.seed(seed)\n",
        "\n",
        "circuit = random_clifford_circuit(num_qubits, depth, rng)\n",
        "circuit.measure_all()\n",
        "circuit.draw(\"mpl\", fold=-1, scale=0.6)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-header",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-for-quantum-hardware-execution\" />\n",
        "\n",
        "### ステップ 2：量子ハードウェアでの実行に向けた最適化\n",
        "\n",
        "回路をハードウェアにマッピングすることで、物理的な量子ビットの配置、候補となるチェックを評価するノイズモデル、およびチェックそのものが決定されます。\n",
        "\n",
        "まず、バックエンドを選択し、そのカップリングマップから、「セットアップ」セクションで定義されたヘルパー `find_check_layout` を含む1次元クビット配置を検索します。 このヘルパーは、誤差が最も大きいゲートや読み出しを回避するランダムな自己回避経路を生成し、ターゲットとアンシラの組み合わせが最も多い経路を選択します。 この検索処理は、バックエンド自体から接続状況やエラーデータを読み取るため、どの IBM Quantum QPUでも同じコードを実行できます。 その後、この関 `get_check_qubits` 数はターゲットとアンシラのペアを返します。ここで、のチェックにはが使用 `target_qubits[i]` されます `ancilla_qubits[i]`。\n",
        "\n",
        "以下の結合グラフでは、緑色の量子ビットがペイロード量子ビットであり、オレンジ色の量子ビットがチェック機能を実装するアンシラです。 隣接するアンシラを持つ量子ビットは、チェックのターゲット量子ビットとして使用される。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "step2-layout",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backend: ibm_boston\n",
            "Target qubits:  [105, 107, 108, 123, 125, 141, 143]\n",
            "Ancilla qubits: [104, 97, 109, 122, 126, 140, 144]\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step2-layout-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(operational=True, simulator=False)\n",
        "\n",
        "print(f\"Backend: {backend.name}\")\n",
        "\n",
        "# Search for a low-error path, then pair each target qubit with a neighboring ancilla\n",
        "layout = find_check_layout(backend, num_qubits, rng)\n",
        "target_qubits, ancilla_qubits = get_check_qubits(backend, layout)\n",
        "num_checks = len(target_qubits)\n",
        "\n",
        "print(f\"Target qubits:  {target_qubits}\")\n",
        "print(f\"Ancilla qubits: {ancilla_qubits}\")\n",
        "plot_coupling_map(\n",
        "    num_qubits=backend.num_qubits,\n",
        "    qubit_coordinates=getattr(\n",
        "        backend.configuration(), \"qubit_coordinates\", None\n",
        "    ),\n",
        "    coupling_map=backend.configuration().coupling_map,\n",
        "    figsize=(12, 12),\n",
        "    qubit_color=[\n",
        "        \"#4CAF50\"\n",
        "        if i in set(layout)\n",
        "        else \"#FF9800\"\n",
        "        if i in set(ancilla_qubits)\n",
        "        else \"#DDDDDD\"\n",
        "        for i in backend.coupling_map.graph.node_indices()\n",
        "    ],\n",
        "    qubit_size=220,\n",
        "    line_width=2,\n",
        "    font_size=90,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-transpile-md",
      "metadata": {},
      "source": [
        "バックエンドとレイアウトが決定したら、ペイロードを命令セットアーキテクチャ（ISA）回路にトランスパイルします。 必要なのは、レイアウトを設定し、ゲートをバックエンドのネイティブゲートセットに変換することだけです。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "step2-transpile",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step2-transpile-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "pm = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=backend, initial_layout=layout\n",
        ")\n",
        "circuit_isa = pm.run(circuit)\n",
        "circuit_isa.draw(\"mpl\", fold=-1, scale=0.6)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-noise-md",
      "metadata": {},
      "source": [
        "次に、バックエンドにおけるゲートノイズおよび読み出しノイズが実行にどのような影響を与えるかをモデル化します。 ノイズモデルによって、回路のどの部分でチェックを行うと最も多くのエラーを検出できるかが決まります。 より精度の高いモデルを使用すれば検出精度は向上しますが、通常、QPUからサンプリングを行ってそのようなモデルを学習させる必要はありません。 以下のモデルは、ベンチマーク `qiskit-ibm-runtime` データから、ゲートノイズおよび読み出しノイズに対する一様脱分極チャネルを推定するものである。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "step2-noise",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "NoiseModel(gate_noise=0.001079865281450939, readout_noise=0.006001790364583333, idling_noise=None)\n"
          ]
        }
      ],
      "source": [
        "noise_model = NoiseModel.from_backend(\n",
        "    backend, layout, uniform_gate_noise=True\n",
        ")\n",
        "print(noise_model)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-checks-md",
      "metadata": {},
      "source": [
        "それでは、回路にチェック機能を追加しましょう。 この関 `add_pauli_checks` 数は、クリフォード・ペイロード、対象クビットのリスト、およびノイズモデルを受け取ります。 この引 `ancilla_qubits` 数は、各ターゲットとどの物理アンシラをペアにするかを関数に指定します。 チェックは、対象となる量子ビットが登場する順に追加されるため、チェック対象の回路の最終的なレイアウトは となります `layout + ancilla_qubits`。 チェックの`i`回数を減らして出力回路を動作させるには、最終的なレイアウトは となります `layout + ancilla_qubits[:i]`。\n",
        "\n",
        "の出力は、チェックの数が徐々に増えていく一連の回路であり、チェックが全くない状態から、各ターゲット量子ビットに対して1つのチェックが行われる状態までを網羅 `add_pauli_checks` しています。 この可視化により、チェックが指定されたターゲットとアンシラのペアを使用していることが確認できます。 適切なチェックを見つける方法の詳細については、参考文献 [\\[1\\]](#references) の補足情報の第II節から第IV節を参照のこと。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "step2-checks",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Physical layout of payload and ancillas: [108, 107, 106, 105, 117, 125, 124, 123, 136, 143, 142, 141, 104, 97, 109, 122, 126, 140, 144]\n",
            "Checked circuit:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step2-checks-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "checked = add_pauli_checks(\n",
        "    circuit_isa,\n",
        "    target_qubits,\n",
        "    noise_model,\n",
        "    ancilla_qubits=ancilla_qubits,\n",
        "    cost=\"gamma\",\n",
        "    method=\"windowed\",\n",
        "    seed=seed,\n",
        ")\n",
        "\n",
        "print(f\"Physical layout of payload and ancillas: {layout + ancilla_qubits}\")\n",
        "print(\"Checked circuit:\")\n",
        "checked[-1].circuit.draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-header",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### ステップ 3: `Qiskit primitives` を使用して実行する\n",
        "\n",
        "ゲートノイズの影響を可視化するには、ペイロードの深さを増やし、そのスタビライザーの一部をサンプリングします。 各安定化演算子は、一般的に他の安定化演算子と量子ビットごとに可換ではないため、1つの検証手順を2つの異なる安定化演算子に適用することはできない。 安定化子を「通勤集合」ごとにまとめるのではなく、各安定化子について個別に、適切な検証集合を見つける。 安定化器を均一に無作為にサンプリングすることで、偏りのない忠実度の推定値が得られる。\n",
        "\n",
        "より複雑な回路を構築し、その安定化素子からランダムにサンプルを抽出してください。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "step3-stabilizers",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Sampled 20 stabilizers of a 12-qubit circuit with two-qubit depth 24: {ZXIIXZYYXIZZ, XXXYIIZYXIII, ...}\n"
          ]
        }
      ],
      "source": [
        "depth = 24\n",
        "num_stabilizers = 20\n",
        "num_shots = 1_000\n",
        "\n",
        "circuit = random_clifford_circuit(num_qubits, depth, rng)\n",
        "\n",
        "# Build the full stabilizer group, then sample from it uniformly at random\n",
        "circ_no_meas = circuit.remove_final_measurements(inplace=False)\n",
        "stabilizer_group = PauliList([Pauli(\"I\" * num_qubits)])\n",
        "for generator in (\n",
        "    Pauli(label) for label in Clifford(circ_no_meas).to_labels(mode=\"S\")\n",
        "):\n",
        "    stabilizer_group = stabilizer_group + stabilizer_group.compose(generator)\n",
        "\n",
        "keep = np.where(\n",
        "    stabilizer_group.x.any(axis=1) | stabilizer_group.z.any(axis=1)\n",
        ")[0]\n",
        "chosen = np.random.default_rng(seed).choice(\n",
        "    keep, size=min(num_stabilizers, len(keep)), replace=False\n",
        ")\n",
        "stabilizers = [stabilizer_group[int(i)] for i in chosen]\n",
        "\n",
        "two_qubit_depth = circuit.depth(lambda x: x.operation.num_qubits == 2)\n",
        "print(\n",
        "    f\"Sampled {len(stabilizers)} stabilizers of a {circuit.num_qubits}-qubit \"\n",
        "    f\"circuit with two-qubit depth {two_qubit_depth}: \"\n",
        "    f\"{{{stabilizers[0]}, {stabilizers[1]}, ...}}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-findchecks-md",
      "metadata": {},
      "source": [
        "サンプリングされた各スタビライザーについて、そのスタビライザーが計算基底で測定されるように回路を回転させ、バックエンドにトランスパイルし、適切なチェックセットを見つける。 各スタビライザーについて、ターゲットとアンシラのペアを一緒にシャッフルし、各ターゲットがそれぞれのアンシラを維持するようにする。 チェックは、対象となる量子ビットが指定された順序で順次確定され、一度確定したチェックは、その後さらにチェックが追加されても変更されないことに注意してください。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "step3-findchecks",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "100%|██████████| 20/20 [00:15<00:00,  1.29it/s]"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Added 7 checks to 20 circuits in 15s.\n",
            "On average, two-qubit depth increased from 24 to 33 when adding 7 checks.\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "\n"
          ]
        }
      ],
      "source": [
        "noisy_circuits = []\n",
        "checked_circuits = []\n",
        "depths_2q = []\n",
        "t0 = time.time()\n",
        "for i, pauli in enumerate(tqdm(stabilizers)):\n",
        "    noisy_circuits.append(pm.run(append_basis_rotation(circuit, pauli)))\n",
        "    # Shuffle target and ancilla pairs together so each target keeps its ancilla\n",
        "    targets, ancillas = zip(\n",
        "        *random.sample(\n",
        "            list(zip(target_qubits, ancilla_qubits, strict=True)),\n",
        "            k=len(target_qubits),\n",
        "        ),\n",
        "        strict=True,\n",
        "    )\n",
        "    checked_circuits.append(\n",
        "        add_pauli_checks(\n",
        "            noisy_circuits[-1],\n",
        "            list(targets),\n",
        "            noise_model,\n",
        "            ancilla_qubits=list(ancillas),\n",
        "            cost=\"gamma\",\n",
        "            method=\"windowed\",\n",
        "            seed=seed + 1 + i,\n",
        "        )\n",
        "    )\n",
        "    depths_2q.append(\n",
        "        checked_circuits[-1][-1].circuit.depth(lambda x: len(x.qubits) == 2)\n",
        "    )\n",
        "\n",
        "print(\n",
        "    f\"Added {num_checks} checks to {len(stabilizers)} circuits \"\n",
        "    f\"in {(time.time() - t0):.0f}s.\"\n",
        ")\n",
        "print(\n",
        "    f\"On average, two-qubit depth increased from \"\n",
        "    f\"{circuit.depth(lambda x: len(x.qubits) == 2)} to {int(np.mean(depths_2q))} \"\n",
        "    f\"when adding {num_checks} checks.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-sample-md",
      "metadata": {},
      "source": [
        "Qiskit Aer を使用して、ベアペイロードと検証済み回路をサンプルとして実行してみてください。 このシミュレータは、チェックの採点に使用されたものと同じ脱分極モデルを採用しているため、チェックの対象となるノイズは、シミュレータが適用するノイズそのものです。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "step3-sample",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "100%|██████████| 20/20 [00:17<00:00,  1.13it/s]\n"
          ]
        }
      ],
      "source": [
        "aer_nm = AerNoiseModel()\n",
        "aer_nm.add_all_qubit_quantum_error(\n",
        "    depolarizing_error(noise_model.gate_noise, 2), [\"cz\"]\n",
        ")\n",
        "p = noise_model.readout_noise\n",
        "aer_nm.add_all_qubit_readout_error(ReadoutError([[1 - p, p], [p, 1 - p]]))\n",
        "noisy_sim = AerSimulator(method=\"stabilizer\", noise_model=aer_nm)\n",
        "\n",
        "counts = []\n",
        "for i, checked_circ_result in enumerate(tqdm(checked_circuits)):\n",
        "    noisy_counts = (\n",
        "        noisy_sim.run(\n",
        "            noisy_circuits[i], shots=num_shots, seed_simulator=seed * i + 1\n",
        "        )\n",
        "        .result()\n",
        "        .get_counts()\n",
        "    )\n",
        "    checked_counts_per_variant = []\n",
        "    for k, ck in enumerate(checked_circ_result):\n",
        "        variant_counts = (\n",
        "            noisy_sim.run(\n",
        "                ck.circuit, shots=num_shots, seed_simulator=seed * i + 2 + k\n",
        "            )\n",
        "            .result()\n",
        "            .get_counts()\n",
        "        )\n",
        "        checked_counts_per_variant.append(variant_counts)\n",
        "    counts.append((noisy_counts, checked_counts_per_variant))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step4-header",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### ステップ4：後処理を行い、結果を所望の従来の形式で出力する\n",
        "\n",
        "各チェックでは、1つのアンシラと1つのターゲットの間でエンタングルメントゲートが使用されます。 このancillaは $|0\\rangle$ から開始されるため、 $Z_\\text{anc}$ によってその入力が安定化されます。 $Z_\\text{anc}$ をチェック回路に前方伝播させると、出力としてパウリ演算子が得られ、その非単位項がチェックのサポートを定義する。 チェックは、そのサポート内のビットのパリティが偶数である場合に合格となります。 すべてのチェックに合格した場合にのみ、サンプルが保持されます。\n",
        "\n",
        "各 は `get_postselection_method` 、測定されたビット列をシンドロームベクトルに写像する関数を返します `CheckedCircuit` 。 すべてのチェックにおいて症候群がゼロであるサンプルを残し、残りは除外する。 次のグラフは、チェックの回数を増やすと、選別後の残存率が低下することを示しています。 ポストセレクション率が低いほど、目標の精度に達するにはより多くのショットが必要となるため、検出能力とサンプリングコストの間にはトレードオフが存在する。 この率は収束しているように見受けられ、これは追加のチェックを行っても検出能力の向上は限定的であることを示唆している。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "step4-psr",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step4-psr-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "rate_per_variant = []\n",
        "kept_per_stab = []\n",
        "for i, (_, checked_counts_per_variant) in enumerate(counts):\n",
        "    rates = []\n",
        "    kept_at_num_checks = None\n",
        "    for k, variant_counts in enumerate(checked_counts_per_variant):\n",
        "        ps_fn = checked_circuits[i][k].get_postselection_method()\n",
        "        kept = {\n",
        "            bs: n for bs, n in variant_counts.items() if not ps_fn(bs).any()\n",
        "        }\n",
        "        rates.append(sum(kept.values()) / num_shots)\n",
        "        if k == num_checks:\n",
        "            kept_at_num_checks = kept\n",
        "    rate_per_variant.append(rates)\n",
        "    kept_per_stab.append(kept_at_num_checks)\n",
        "\n",
        "max_len = max(len(s) for s in rate_per_variant)\n",
        "rates_arr = np.full((len(rate_per_variant), max_len), np.nan)\n",
        "for i, s in enumerate(rate_per_variant):\n",
        "    rates_arr[i, : len(s)] = s\n",
        "ks = np.arange(max_len)\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "ax.plot(ks, rates_arr.T, color=\"#ff8c00\", alpha=0.15, linewidth=1)\n",
        "ax.plot(\n",
        "    ks,\n",
        "    np.nanmedian(rates_arr, axis=0),\n",
        "    color=\"black\",\n",
        "    linewidth=1,\n",
        "    linestyle=\"--\",\n",
        "    label=\"median\",\n",
        ")\n",
        "ax.set_xlabel(\"Checks committed\")\n",
        "ax.set_ylabel(\"Postselection rate\")\n",
        "ax.set_ylim((0, 1.05))\n",
        "ax.set_title(\n",
        "    f\"Per-stabilizer postselection rate ({len(rates_arr)} stabilizers)\"\n",
        ")\n",
        "ax.legend()\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step4-fidelity-md",
      "metadata": {},
      "source": [
        "それでは、ノイズを含む素の状態と、事後選択された状態の忠実度を比較してみましょう。 エラーが検出されなかったサンプルのみを事後選択することで、すべてのスタビライザーの期待値が上昇し、その結果、推定フィデリティも向上する。 ポスト選択された値は、生データよりも少ないサンプル数で算出されるにもかかわらず、期待値はより正確であり、サンプリング分散も小さくなります。 また、選択後の平均率は、ノイズを含む忠実度に近似していることにも注目してください。 チェックによって誤ったサンプルのほぼすべてが検出される場合、予想されるのは次のような現象です。すなわち、すべてのチェックに合格するサンプルの割合が、誤りのないサンプルの割合に近づいていくのです。これが、ノイズを含む状態の忠実度となります。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "step4-fidelity",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "ideal fidelity:          1.0\n",
            "noisy fidelity:          0.7899\n",
            "postselected fidelity:   0.9679\n",
            "mean postselection rate: 0.780\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step4-fidelity-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step4-fidelity-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "results = []\n",
        "for i, ((noisy_counts, _), kept) in enumerate(\n",
        "    zip(counts, kept_per_stab, strict=True)\n",
        "):\n",
        "    results.append(\n",
        "        (\n",
        "            expectation(noisy_counts, stabilizers[i]),\n",
        "            expectation(kept, stabilizers[i]),\n",
        "            sum(kept.values()) / num_shots,\n",
        "        )\n",
        "    )\n",
        "\n",
        "fidelity_noisy = float(np.nanmean([r[0] for r in results]))\n",
        "fidelity_postsel = float(np.nanmean([r[1] for r in results]))\n",
        "psr = float(np.mean([r[2] for r in results]))\n",
        "print(\n",
        "    f\"ideal fidelity:          1.0\\n\"\n",
        "    f\"noisy fidelity:          {fidelity_noisy:.4f}\\n\"\n",
        "    f\"postselected fidelity:   {fidelity_postsel:.4f}\\n\"\n",
        "    f\"mean postselection rate: {psr:.3f}\"\n",
        ")\n",
        "\n",
        "evs_ideal = np.ones(len(results))\n",
        "evs_noisy = np.array([r[0] for r in results])\n",
        "evs_post = np.array([r[1] for r in results])\n",
        "idx = np.arange(len(results))\n",
        "\n",
        "\n",
        "def strip(ax, ys, color, label):\n",
        "    m, s = np.nanmean(ys), np.nanstd(ys)\n",
        "    ax.axhspan(\n",
        "        m - s, m + s, color=color, alpha=0.15, label=f\"{label} mean and std\"\n",
        "    )\n",
        "    ax.axhline(\n",
        "        m, color=color, linewidth=1, linestyle=\"--\", label=f\"{label} fidelity\"\n",
        "    )\n",
        "\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "ax.axhline(np.nanmean(evs_ideal), color=\"black\", linewidth=1.5, label=\"ideal\")\n",
        "strip(ax, evs_noisy, \"red\", \"noisy\")\n",
        "strip(ax, evs_post, \"green\", \"postselected\")\n",
        "ax.scatter(idx, evs_noisy, color=\"red\", s=22, alpha=0.7, label=\"noisy EVs\")\n",
        "ax.scatter(\n",
        "    idx,\n",
        "    evs_post,\n",
        "    color=\"green\",\n",
        "    s=22,\n",
        "    alpha=0.7,\n",
        "    label=\"postselected EVs\",\n",
        ")\n",
        "ax.set_xlabel(\"stabilizer index\")\n",
        "ax.set_ylabel(r\"$\\langle G \\rangle$\")\n",
        "ax.set_ylim((-0.1, 1.1))\n",
        "ax.set_title(\"Per-stabilizer expectation values\")\n",
        "ax.legend(loc=\"lower left\")\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()\n",
        "\n",
        "M = np.arange(1, len(results) + 1)\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "for ys, color, label in [\n",
        "    (evs_ideal, \"black\", \"ideal\"),\n",
        "    (evs_noisy, \"red\", \"noisy\"),\n",
        "    (evs_post, \"green\", \"postselected\"),\n",
        "]:\n",
        "    cm, sem = cum_mean_sem(ys)\n",
        "    ax.plot(M, cm, color=color, linewidth=1.5, label=label)\n",
        "    ax.fill_between(M, cm - sem, cm + sem, color=color, alpha=0.15)\n",
        "ax.set_xlabel(\"number of stabilizers averaged\")\n",
        "ax.set_ylabel(\"running fidelity estimate\")\n",
        "ax.set_title(\"Fidelity convergence versus number of stabilizers\")\n",
        "ax.legend()\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step4-gamma-md",
      "metadata": {},
      "source": [
        "ガンマスコアは、モデル化されたノイズチャネルのうち、どの程度の部分がチェックによって検出されなかったかを示します。 ガンマスコアを「コミットされたチェックの数」に対してプロットすると、チェックが追加されるごとに検出能力が向上していく様子がわかります。 の値は、チェックによってモデル化されたノイズがすべて捕捉されていることを意味 `1.0` します。 チェックのコミット数が増える `1.0` につれて、曲線は の方向に下がっていきます。これは、チェックが1つ追加されるごとに、残りの未検出エラーの一部が捕捉されることを示しています。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "step4-gamma",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step4-gamma-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "stab_scores = [\n",
        "    [variant.cost for variant in checked_circ_result]\n",
        "    for checked_circ_result in checked_circuits\n",
        "]\n",
        "max_len = max(len(s) for s in stab_scores)\n",
        "scores = np.full((len(stab_scores), max_len), np.nan)\n",
        "for i, s in enumerate(stab_scores):\n",
        "    scores[i, : len(s)] = s\n",
        "ks = np.arange(max_len)\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "ax.plot(ks, scores.T, color=\"#4682b4\", alpha=0.15, linewidth=1)\n",
        "ax.plot(\n",
        "    ks,\n",
        "    np.nanmedian(scores, axis=0),\n",
        "    color=\"black\",\n",
        "    linewidth=1,\n",
        "    linestyle=\"--\",\n",
        "    label=\"median\",\n",
        ")\n",
        "ax.set_xlabel(\"Checks committed\")\n",
        "ax.set_ylabel(\"Gamma\")\n",
        "ax.set_yscale(\"log\")\n",
        "ax.set_title(f\"Per-stabilizer gamma curves ({len(scores)} stabilizers)\")\n",
        "ax.legend()\n",
        "ax.grid(True, alpha=0.3, which=\"both\")\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hardware-header",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## 大規模なハードウェアの例\n",
        "\n",
        "同じワークフローが、より大容量でより深いペイロードを備えたハードウェア上でも実行されます。 このセクションでは、シミュレータの例で使用したバックエンドを再利用しますが、独自のターゲットおよびアンシラペア、パスマネージャを備えた新しい20キュービットのレイアウトを構築し、その回路を単一のジョブとしてQPUに送信します。 このサイズでは、ほとんどのショットで少なくとも1回のチェックがトリガーされるため、ポストセレクション率は低く、各回路では十分なサンプル数を確保するために多量のショット予算が必要となります。 したがって、この例では、予算を少数のサンプリングされたスタビライザーに集中させています。これは依然として不偏の忠実度推定値ですが、シミュレータの例のように多数のスタビライザーの平均値を用いた場合よりも精度が粗くなります。\n",
        "\n",
        "シミュレータの例と比べて、1つ異なる点があります。それは、キャリブレーションデータから一様な脱分極チャネルを推定する代わりに、このセクションではノイズモデルを**学習し**、その結果を用いてモデル `qiskit-paulice` を構築する点 `NoiseLearner` です `NoiseModel.from_pauli_lindblad_maps`。 学習済みパウリ・リンドブラッドモデルは、すべてのエッジでノイズの程度が等しいと仮定するのではなく、この特定のレイアウトにおけるノイズの空間的構造を捉えているため、チェックの配置評価は、QPUに影響を与えるノイズにより近い条件に基づいて行われる。 ノイズの学習にはQPUのサンプリングが必要であり、QPU全体のサンプリング予算においてこれを考慮に入れる必要があります。\n",
        "\n",
        "以下のパラメータは、量子ビット数、反復回数、安定化演算子の数、および実行回数を設定します。 ポストセレクション率の逆数 `hw_num_shots` に比例する：3％の率の場合、40,000ショットのうち、1回路あたり約1,200個のポストセレクションされたサンプルが残る。 この `hw_num_stabilizers` 値を大きくすると、忠実度の推定精度が向上しますが、その代償としてジョブあたりの回路数が増加し、各回路には同じショット予算が必要となります。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hardware-steps-header",
      "metadata": {},
      "source": [
        "<span id=\"steps-1-4-compressed-into-a-single-code-block\" />\n",
        "\n",
        "### 手順 1～4（1つのコードブロックにまとめました）\n",
        "\n",
        "次のセルでは、シミュレータの例と同じ4つの手順が実行されます。 より大規模なペイロードを構築し、いくつかのスタビライザーをサンプリングします（ステップ1）。 レイアウトを選択し、そのレイアウト上でノイズモデルを学習させ、各スタビライザーについて完全にチェック済みの回路を特定します（ステップ2）；ベア回路とチェック済み回路の両方を含む1つのSamplerジョブを提出します（ステップ3）；そして、スタビライザーごとおよび平均値について、チェック済みカウントをポストセレクションし、ノイズの影響を受けたフィデリティ推定値とポストセレクション後のフィデリティ推定値を比較します（ステップ4）。 この規模では、シミュレータの例のように安定化子群全体を列挙することは現実的ではないため、セルは安定化子のランダムな部分集合をサンプリングして、忠実度の推定値を算出する。\n",
        "\n",
        "ステップ2ではシミュレーターの例よりも多くの処理が行われていることに注意してください。ノイズモデルを学習すると、独自のノイズモデルが送信されます。 `NoiseLearner` サンプラージョブの前にジョブが実行されるため、セルは合計で **2つの**ジョブを実行します。 後でそれらを見つけられるように `TUT_ASPC` 、タグと `TUT_ASPC_LEARN` が付いています。 求人へのタグ付けに関する詳細は、「 [求人タグによる整理と検索](/docs/guides/add-job-tags)」 をご覧ください。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "hardware-submit",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Layout with 11 check pairs: [153, 152, 151, 138, 131, 130, 129, 118, 109, 110, 111, 98, 91, 90, 89, 78, 69, 70, 71, 58]\n",
            "Submitted noise-learner job d9f4mncjeosc73fjfmkg\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Learned 4 layers; readout noise 0.00470\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "100%|██████████| 10/10 [01:01<00:00,  6.18s/it]\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Submitted job d9f4s04jeosc73fjftkg with 20 circuits\n",
            "noisy fidelity estimate:        0.3685\n",
            "postselected fidelity estimate: 0.6869\n",
            "mean postselection rate:        0.2851 (~11404 kept shots per circuit)\n",
            "\n",
            "per-stabilizer results:\n",
            "idx     noisy   postsel      psr  kept shots\n",
            "  0    0.3769    0.6918   0.3247       12987\n",
            "  1    0.3745    0.6760   0.2999       11995\n",
            "  2    0.3659    0.6389   0.3549       14196\n",
            "  3    0.3821    0.7060   0.2660       10641\n",
            "  4    0.3653    0.7475   0.2531       10124\n",
            "  5    0.3752    0.7022   0.2698       10791\n",
            "  6    0.3508    0.7144   0.2711       10842\n",
            "  7    0.3485    0.7087   0.2381        9523\n",
            "  8    0.3825    0.6289   0.2928       11711\n",
            "  9    0.3630    0.6549   0.2808       11232\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/hardware-submit-5.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# -------------------------Step 1: build a larger payload and sample stabilizers-------------------------\n",
        "hw_num_qubits = 20\n",
        "hw_depth = 36\n",
        "hw_num_stabilizers = 10\n",
        "hw_num_shots = 40_000\n",
        "\n",
        "hw_circuit = random_clifford_circuit(hw_num_qubits, hw_depth, rng)\n",
        "hw_no_meas = hw_circuit.remove_final_measurements(inplace=False)\n",
        "\n",
        "# Enumerating all 2^n stabilizers is infeasible at this size, so draw each\n",
        "# stabilizer by composing a random subset of the group generators\n",
        "hw_generators = [\n",
        "    Pauli(label) for label in Clifford(hw_no_meas).to_labels(mode=\"S\")\n",
        "]\n",
        "sample_rng = np.random.default_rng(seed)\n",
        "hw_stabilizers = []\n",
        "while len(hw_stabilizers) < hw_num_stabilizers:\n",
        "    mask = sample_rng.integers(0, 2, hw_num_qubits).astype(bool)\n",
        "    if not mask.any():\n",
        "        continue  # skip the identity\n",
        "    stabilizer = Pauli(\"I\" * hw_num_qubits)\n",
        "    for generator, chosen in zip(hw_generators, mask, strict=True):\n",
        "        if chosen:\n",
        "            stabilizer = stabilizer.compose(generator)\n",
        "    hw_stabilizers.append(stabilizer)\n",
        "\n",
        "# -------------------------Step 2: find a 20-qubit layout, learn its noise, and add checks-------------------------\n",
        "# A single bad coupler or bad-readout qubit on the path drags every\n",
        "# stabilizer down, so search harder and with tighter error thresholds\n",
        "hw_layout = find_check_layout(\n",
        "    backend,\n",
        "    hw_num_qubits,\n",
        "    rng,\n",
        "    num_trials=500,\n",
        "    max_gate_error=0.015,\n",
        "    max_readout_error=0.05,\n",
        ")\n",
        "hw_target_qubits, hw_ancilla_qubits = get_check_qubits(backend, hw_layout)\n",
        "hw_pm = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=backend, initial_layout=hw_layout\n",
        ")\n",
        "print(f\"Layout with {len(hw_target_qubits)} check pairs: {hw_layout}\")\n",
        "\n",
        "# ----- learn a Pauli-Lindblad noise model on this layout -----\n",
        "# The simulator example scored checks against a uniform depolarizing channel\n",
        "# inferred from calibration data. Here, learn the noise instead: NoiseLearner\n",
        "# runs its own job on the QPU and returns a Pauli-Lindblad channel per unique\n",
        "# entangling layer, so the checks are placed against the noise this layout\n",
        "# actually has, including its spatial structure. All the sampled stabilizers\n",
        "# share the same entangling layers and differ only in their final basis\n",
        "# rotation, so learning on the bare payload covers all of them.\n",
        "learner = NoiseLearner(\n",
        "    mode=backend,\n",
        "    options={\n",
        "        \"max_layers_to_learn\": 4,\n",
        "        \"num_randomizations\": 32,\n",
        "        \"shots_per_randomization\": 128,\n",
        "        \"environment\": {\"job_tags\": [\"TUT_ASPC_LEARN\"]},\n",
        "    },\n",
        ")\n",
        "learner_job = learner.run([hw_pm.run(hw_circuit)])\n",
        "print(f\"Submitted noise-learner job {learner_job.job_id()}\")\n",
        "hw_layer_errors = learner_job.result().data\n",
        "\n",
        "# To see how much the learned model helps, swap the next line for the\n",
        "# simulator example's uniform model - a one-line change:\n",
        "# hw_noise_model = NoiseModel.from_backend(backend, hw_layout, uniform_gate_noise=True)\n",
        "hw_noise_model = learned_noise_model(hw_layer_errors, hw_layout)\n",
        "# NoiseLearner characterizes gate noise only, so keep the readout estimate\n",
        "# from calibration data rather than leaving it unset\n",
        "hw_noise_model.readout_noise = NoiseModel.from_backend(\n",
        "    backend, hw_layout, uniform_gate_noise=True\n",
        ").readout_noise\n",
        "print(\n",
        "    f\"Learned {len(hw_layer_errors)} layers; \"\n",
        "    f\"readout noise {hw_noise_model.readout_noise:.5f}\"\n",
        ")\n",
        "\n",
        "# ----- add the fully checked circuit per stabilizer -----\n",
        "hw_noisy_circuits = []\n",
        "hw_checked_circuits = []\n",
        "for i, pauli in enumerate(tqdm(hw_stabilizers)):\n",
        "    bare = hw_pm.run(append_basis_rotation(hw_circuit, pauli))\n",
        "    hw_noisy_circuits.append(bare)\n",
        "    variants = add_pauli_checks(\n",
        "        bare,\n",
        "        hw_target_qubits,\n",
        "        hw_noise_model,\n",
        "        ancilla_qubits=hw_ancilla_qubits,\n",
        "        cost=\"gamma\",\n",
        "        method=\"windowed\",\n",
        "        seed=seed + 1 + i,\n",
        "    )\n",
        "    hw_checked_circuits.append(variants[-1])  # keep the fully checked circuit\n",
        "\n",
        "# -------------------------Step 3: submit one Sampler job with the bare and checked circuits-------------------------\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.default_shots = hw_num_shots\n",
        "sampler.options.environment.job_tags = [\"TUT_ASPC\"]\n",
        "\n",
        "pubs = hw_noisy_circuits + [cc.circuit for cc in hw_checked_circuits]\n",
        "job = sampler.run(pubs)\n",
        "print(f\"Submitted job {job.job_id()} with {len(pubs)} circuits\")\n",
        "\n",
        "# -------------------------Step 4: postselect and compare fidelity-------------------------\n",
        "result = job.result()\n",
        "n_stab = len(hw_stabilizers)\n",
        "\n",
        "hw_results = []\n",
        "for i in range(n_stab):\n",
        "    noisy_counts = result[i].join_data().get_counts()\n",
        "    checked_counts = result[n_stab + i].join_data().get_counts()\n",
        "    ps_fn = hw_checked_circuits[i].get_postselection_method()\n",
        "    kept = {bs: c for bs, c in checked_counts.items() if not ps_fn(bs).any()}\n",
        "    hw_results.append(\n",
        "        (\n",
        "            expectation(noisy_counts, hw_stabilizers[i]),\n",
        "            expectation(kept, hw_stabilizers[i]),\n",
        "            sum(kept.values()) / sum(checked_counts.values()),\n",
        "        )\n",
        "    )\n",
        "\n",
        "hw_fidelity_noisy = float(np.nanmean([r[0] for r in hw_results]))\n",
        "hw_fidelity_postsel = float(np.nanmean([r[1] for r in hw_results]))\n",
        "hw_psr = float(np.mean([r[2] for r in hw_results]))\n",
        "print(\n",
        "    f\"noisy fidelity estimate:        {hw_fidelity_noisy:.4f}\\n\"\n",
        "    f\"postselected fidelity estimate: {hw_fidelity_postsel:.4f}\\n\"\n",
        "    f\"mean postselection rate:        {hw_psr:.4f} \"\n",
        "    f\"(~{int(round(hw_psr * hw_num_shots))} kept shots per circuit)\"\n",
        ")\n",
        "\n",
        "# Per-stabilizer breakdown. The postselection rate varies from stabilizer to\n",
        "# stabilizer, so a stabilizer whose postselected value barely moves is usually\n",
        "# one whose checks rejected little; the kept-shot count says how much of the\n",
        "# gap is statistics rather than signal.\n",
        "print(\"\\nper-stabilizer results:\")\n",
        "print(\n",
        "    f\"{'idx':>3}  {'noisy':>8}  {'postsel':>8}  {'psr':>7}  {'kept shots':>10}\"\n",
        ")\n",
        "for i, (noisy, post, psr_i) in enumerate(hw_results):\n",
        "    print(\n",
        "        f\"{i:>3}  {noisy:>8.4f}  {post:>8.4f}  {psr_i:>7.4f}  \"\n",
        "        f\"{int(round(psr_i * hw_num_shots)):>10}\"\n",
        "    )\n",
        "\n",
        "hw_noisy = np.array([r[0] for r in hw_results])\n",
        "hw_post = np.array([r[1] for r in hw_results])\n",
        "idx = np.arange(n_stab)\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(9, 4))\n",
        "ax.axhline(1.0, color=\"black\", linewidth=1.5, label=\"ideal\")\n",
        "strip(ax, hw_noisy, \"red\", \"noisy\")\n",
        "strip(ax, hw_post, \"green\", \"postselected\")\n",
        "ax.scatter(idx, hw_noisy, color=\"red\", s=22, alpha=0.7, label=\"noisy EVs\")\n",
        "ax.scatter(\n",
        "    idx,\n",
        "    hw_post,\n",
        "    color=\"green\",\n",
        "    s=22,\n",
        "    alpha=0.7,\n",
        "    label=\"postselected EVs\",\n",
        ")\n",
        "ax.set_xlabel(\"stabilizer index\")\n",
        "ax.set_ylabel(r\"$\\langle G \\rangle$\")\n",
        "ax.set_ylim((-0.1, 1.1))\n",
        "ax.set_xticks(idx)\n",
        "ax.set_title(\"Per-stabilizer expectation values on hardware\")\n",
        "# Outside the axes so it cannot hide a data point\n",
        "ax.legend(loc=\"center left\", bbox_to_anchor=(1.02, 0.5), frameon=False)\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hardware-results-md",
      "metadata": {},
      "source": [
        "この規模の回路の場合、ほとんどのサンプルには少なくとも1つのエラーが検出されるため、ポストセレクション率は低く、ポストセレクションによってほとんどのショットが除外されてしまう。 すべてのチェックに合格したサンプルは、素の回路よりもはるかに優れた期待値を示しており、各スタビライザーごとの値は、ノイズの多いベースラインから明確に区別されています。 忠実度の推定精度を高めるには、回路ごとのショット数の上限を同じまま、より多くのスタビライザーをサンプリングします。 ポストセレクション率を高めるには、回路の深さを減らすか、チェックの数を減らす。ペイロードを拡大するには、ポストセレクション率の逆数に応じてショット予算を拡大する。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "next-steps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 次のステップ\n",
        "\n",
        "<Admonition type=\"tip\" title=\"推奨事項\">\n",
        "  この作品に興味を持たれた方は、以下の資料もご参照ください：\n",
        "\n",
        "  * 量子誤り訂正の導入として、 [反復コード](/docs/tutorials/repetition-codes)に関するチュートリアル。\n",
        "  * チェック検出API全体の[ドキュメント `qiskit-paulice`](/docs/addons/qiskit-addon-paulice) 、およびソースコードが格納されているパッケージの [GitHub リポジトリ](https://github.com/Qiskit/qiskit-paulice)。\n",
        "  * [「空間時間符号を用いた低オーバーヘッドのエラー検出」](https://arxiv.org/abs/2504.15725) という論文は、チェックの理論的背景について論じている。\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "references",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## 参照\n",
        "\n",
        "* \\[1] Martiel, S., & Javadi-Abhari, A. (2025). 時空間符号を用いた低オーバーヘッドのエラー検出。 *arXiv プレプリント* [arXiv:2504.15725](https://arxiv.org/abs/2504.15725)。\n",
        "* \\[2] ヴァン・デン・ベルグ, E., Bravyi, S., ガンベッタ, J. M., Jurcevic, P., Maslov, D. および Temme, K. (2023). コヒーレント・パウリチェックによるシングルショット誤差の低減。 『*Physical Review Research* 』, 5(3), 033193.\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": 2.5,
    "qpuSeconds": 10
  },
  "nbformat": 4,
  "nbformat_minor": 5
}