{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"パウリ伝播を用いたノイズを含む量子系のシミュレーション\"\n",
        "description: \"最新バージョンのパウリ伝播を用いて、ノイズを含む量子系をシミュレートする\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "91eb1163-e828-4c59-a06d-e4f0030f8071",
      "metadata": {},
      "source": [
        "<span id=\"simulate-noisy-quantum-systems-with-pauli-propagation\" />\n",
        "\n",
        "# パウリ伝播を用いたノイズを含む量子系のシミュレーション\n",
        "\n",
        "{/* cspell:ignore mathscr, mapsto */}\n",
        "\n",
        "このガイドでは、 3x3 の正方格子上で、ノイズを含む9量子ビットの横磁場イジングモデル（TFIM）の時間的ダイナミクスを古典的にシミュレーションするために、パッケージ `pauli-prop` を使用します。 [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) の指示を用いて、一連のエンタングルメント層（ $\\mathcal{U}$ ）に作用するノイズチャネル（ $\\Lambda$ ）を定義する。次に、観測量（ $O$ ）をノイズの混入した回路を通じて後方伝播させ、さまざまなノイズモデルおよびノイズのない場合について、期待値を推定する。\n",
        "\n",
        "![騒音の多いEV](https://quantum.cloud.ibm.com/docs/images/addons/pauli-prop/noisy_ev.avif)\n",
        "\n",
        "観測量が回路を逆方向に伝播するにつれて、エンタングルメント層 $\\mathcal{U}_k$ に関連する各ノイズチャネル $\\Lambda_k$ は、 $O$ における、そのパウリ・リンドブラッド生成子と反交換するパウリ項を減衰させる。 具体的には、 $G_{k,i}$ が $\\Lambda_k$ のパウリ生成子であり、そのレートが $\\gamma_{k,i}$ である場合、 $O$ に含まれるパウリ項 $P$ は、次のように変換される： $c_P \\mapsto c_P e^{-2\\gamma_{k,i}} \\quad \\text{if } \\{P, G_{k,i}\\}=0$。ここで、 $c_P$ は $P$ の係数である。 $O$ が回路の先頭まで伝播されると、ゼロ状態に対する期待値 $|0\\rangle^{\\otimes N}$ は、 $O$ に含まれる各対角項（すべての量子ビットに $Z$ または $I$ を含む項）の係数を合計することで、自明に計算できる。\n",
        "\n",
        "ワークフロー:\n",
        "\n",
        "* TFIM格子を指定し、エッジの着色を用いて、エンタングルメント層の最小集合を特定する\n",
        "* 各固有のエンタングルメント層について、合成ノイズモデル（ $\\Lambda_k$ ）を生成する。 $U_k$\n",
        "  * さまざまなスケールのノイズモデルを作成し、ゲートノイズがシステムに与える影響を調査する\n",
        "* 対象とするさまざまな深さおよびノイズスケールに対応した、ノイズのない量子回路とノイズのある量子回路を作成する\n",
        "  * ノイズの多い回路では、各エンタングルメント層の前に命令 `PauliLindbladError` が挿入される\n",
        "* パウリ伝播を用いて、さまざまな深さにおける系の正確な期待値をシミュレーションする\n",
        "  * 9量子ビットの場合、これは $O$ を $4^9$ 項まで拡張することで行われ、これによりパウリ空間全体を網羅することになる\n",
        "* パウリ伝播を用いて、ノイズを含む期待値をシミュレートする\n",
        "* ゲートノイズの増加が量子モデルの精度をどのように低下させるかを観察する\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b70d9f4d-2771-45fb-98ac-3c2c4ce978b5",
      "metadata": {},
      "source": [
        "<span id=\"generate-a-3x3-square-lattice-and-find-a-4-coloring-on-the-edges\" />\n",
        "\n",
        "## 3x3 の正方格子を作成し、辺に対する4色着色法を求める\n",
        "\n",
        "グラフの頂点は量子ビットを表し、辺は2つの量子ビット間の接続を表しています。 エッジの色付けは、量子回路内の固有のエンタングルメント層に対応しており、異なる色に割り当てられた接続上のゲートは同時に適用できないようになっている。\n",
        "\n",
        "各層のノイズは個別に学習する必要があるため、効率的なノイズ学習プロトコルを実装するには、最小限の固有のエンタングルメント層の集合を特定することがしばしば重要となる。 習得すべきレイヤーが多ければ多いほど、QPUからのショット数も増やす必要があります。 このデモでは、レイヤー情報を用いてノイズの多い回路を構築し、各エンタングルメント層の前に [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html)`qiskit-aer` 命令を挿入することで、QPUのゲートノイズをモデル化しています。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "58bcd725-3447-4bfc-8d63-12fd58082ff7",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [],
      "source": [
        "from collections import defaultdict\n",
        "\n",
        "import numpy as np\n",
        "from qiskit.transpiler import CouplingMap\n",
        "from qiskit_addon_utils.coloring import auto_color_edges\n",
        "\n",
        "# Define rectangular square-lattice on 20 qubits\n",
        "num_rows = 3\n",
        "num_cols = 3\n",
        "num_qubits = num_rows * num_cols\n",
        "\n",
        "coupling_map = CouplingMap.from_grid(\n",
        "    num_rows=num_rows, num_columns=num_cols, bidirectional=False\n",
        ")\n",
        "\n",
        "# Create mapping from color to edge list\n",
        "coloring = auto_color_edges(coupling_map.get_edges())\n",
        "color_to_edge = defaultdict(list)\n",
        "for edge, color in coloring.items():\n",
        "    color_to_edge[color].append(edge)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "adbceb93-6344-465c-9c4a-1c6e98ebc3d6",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": [
          "remove-input"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The circuit will have 9 qubits and 4 unique entangling layers.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/pauli-prop/guides/simulate-noisy-expectation-values/extracted-outputs/adbceb93-6344-465c-9c4a-1c6e98ebc3d6-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from rustworkx import PyDiGraph\n",
        "from rustworkx.visualization import graphviz_draw\n",
        "\n",
        "# Inspect graph coupling and unique entangling layers\n",
        "print(\n",
        "    f\"The circuit will have {num_qubits} qubits and {len(color_to_edge)} unique entangling layers.\"\n",
        ")\n",
        "sq_lattice = PyDiGraph()\n",
        "sq_lattice.extend_from_weighted_edge_list(\n",
        "    [\n",
        "        (source, target, color)\n",
        "        for ((source, target), color) in coloring.items()\n",
        "    ]\n",
        ")\n",
        "\n",
        "\n",
        "def color_edge_4color(edge):\n",
        "    color_dict = {0: \"red\", 1: \"green\", 2: \"blue\", 3: \"orange\"}\n",
        "    return {\"color\": color_dict[edge]}\n",
        "\n",
        "\n",
        "graphviz_draw(sq_lattice, edge_attr_fn=color_edge_4color, method=\"neato\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "538ccd51-897b-4794-aaff-864d0cea02b3",
      "metadata": {},
      "source": [
        "<span id=\"generate-synthetic-noise-models\" />\n",
        "\n",
        "## 合成ノイズモデルを生成する\n",
        "\n",
        "量子回路を作成する前に、各エンタングルメント層ごとにノイズモデル（ [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) インスタンス）を生成します。 これらは後で、量子回路の命令として組み込むことになります。 各レイヤーについて、さまざまなスケールのノイズチャネルを生成します。 具体的には、 [Error Per Layered Gate（EPLG）](https://www.ibm.com/quantum/blog/quantum-metric-layer-fidelity) がおよそ および `.0004, .0008, .0012, .0016,` となるノイズモデルを生成する `.002`。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "3b3bb04e-d8f7-429f-b396-258ccd7197da",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp, pauli_basis\n",
        "from qiskit_aer.noise import PauliLindbladError\n",
        "\n",
        "# Pauli-Lindblad noise parameters\n",
        "seed = 1764\n",
        "target_EPLGs = [0.0004, 0.0008, 0.0012, 0.0016, 0.002]\n",
        "\n",
        "\n",
        "def generate_random_pauli_lindblad_noise(\n",
        "    edges,\n",
        "    num_qubits: int | None = None,\n",
        "    noise_scale: float = 1e-3,\n",
        "    seed: int | None = None,\n",
        ") -> PauliLindbladError:\n",
        "    \"\"\"Generate random Pauli-Lindblad noise over the full Pauli basis.\"\"\"\n",
        "    if num_qubits is None:\n",
        "        num_qubits = np.max(edges)\n",
        "\n",
        "    basis_paulis = [p for p in pauli_basis(2) if np.sum(p.x + p.z)]\n",
        "    basis_paulis = SparsePauliOp.from_sparse_list(\n",
        "        [\n",
        "            (pauli.to_label(), edge, 1)\n",
        "            for pauli in basis_paulis\n",
        "            for edge in edges\n",
        "        ],\n",
        "        num_qubits=num_qubits,\n",
        "    )\n",
        "    basis_paulis = basis_paulis.simplify()\n",
        "    basis_paulis = basis_paulis.paulis\n",
        "\n",
        "    rng = np.random.default_rng(seed=seed)\n",
        "    rates = rng.random(len(basis_paulis)) * noise_scale\n",
        "\n",
        "    return PauliLindbladError(generators=basis_paulis, rates=rates)\n",
        "\n",
        "\n",
        "num_generators = (\n",
        "    (num_rows * num_cols)\n",
        "    + (num_rows - 1) * num_cols\n",
        "    + num_rows * (num_cols - 1)\n",
        ")\n",
        "noise_scales = [\n",
        "    EPLG * (num_rows * num_cols) / num_generators for EPLG in target_EPLGs\n",
        "]\n",
        "noise_models_per_EPLG = [\n",
        "    [\n",
        "        generate_random_pauli_lindblad_noise(\n",
        "            color_to_edge[color],\n",
        "            num_qubits=num_qubits,\n",
        "            noise_scale=noise_scale,\n",
        "            seed=seed,\n",
        "        )\n",
        "        for color in range(len(color_to_edge))\n",
        "    ]\n",
        "    for noise_scale in noise_scales\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5e2044c3-d265-4de3-b489-acfb4b4666de",
      "metadata": {},
      "source": [
        "<span id=\"create-the-quantum-circuits\" />\n",
        "\n",
        "## 量子回路を作成する\n",
        "\n",
        "このデモでは、横磁場アイジングモデル（TFIM）の時間的挙動を、トロッターステップ数を増やしながら（1～10ステップ）、シミュレーションします。 10種類の回路深度それぞれについて、さまざまなスケールのノイズモデル（`EPLGs = .0004, .0008, .0012, .0016, .002`）を用いて、ゲートノイズの影響をシミュレーションした。ノイズは、Qiskit Aerの [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) 命令 `QuantumCircuit` として回路に挿入される。 検討対象となるハミルトニアンは以下の通りである：\n",
        "\n",
        "$H = -J\\sum\\limits_{\\langle i,j \\rangle} Z_iZ_j + h\\sum\\limits_iX_i$\n",
        "\n",
        "ここで、 $J>0$ は最近接スピン間の結合を表し、 $i<j$、 $h$ は全横磁場である。\n",
        "\n",
        "ここでは、さまざまな時間スケールおよびノイズスケールにわたって、時間発展したハミルトニアンを実装する。 合計60個の回路を作成します。具体的には、トロッター深度が異なるノイズのない回路を10個、および5つのノイズスケールにわたる10種類のトロッター深度に対応するノイズのある回路を50個です。 ある接続グラフに対して、このモデルはいくつかの変数によってパラメータ化される：\n",
        "\n",
        "* `num_steps`: トロッターのステップ数\n",
        "* `J`: 連結されたサイトの結合強度\n",
        "* `h`: 外部磁場の強さ\n",
        "* `dt`: トロッター歩における時間の変化\n",
        "* `initial_state_angle`: すべての量子ビットに均一に印加される初期励起、 $R_y(\\theta)$\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "91e18295-cd56-4245-a4ae-fa30ffb9cd14",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "10 noiseless and 50 noisy Trotter circuits generated. 10 different depths across 5 different noise models\n",
            "\n",
            "Below: Initial state and one noisy Trotter step.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/pauli-prop/guides/simulate-noisy-expectation-values/extracted-outputs/91e18295-cd56-4245-a4ae-fa30ffb9cd14-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from typing import Any\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "\n",
        "# Ising model parameters\n",
        "num_steps = 10\n",
        "J = -1.0\n",
        "dt = 0.25 / abs(J)\n",
        "h = 2.0 * abs(J)\n",
        "initial_state_angle = np.pi / 18.0\n",
        "rx_angle = 2.0 * h * dt\n",
        "rzz_angle = 2.0 * J * dt\n",
        "\n",
        "\n",
        "def generate_ising_circuit(\n",
        "    num_qubits: int,\n",
        "    num_steps: int,\n",
        "    rx_angle: float,\n",
        "    rzz_angle: float,\n",
        "    coloring: dict[Any, list[tuple[int, int]]],\n",
        "    layer_noise_models: list[PauliLindbladError] | None = None,\n",
        "    initial_state_angle: float | None = None,\n",
        ") -> QuantumCircuit:\n",
        "    \"\"\"Generate a quantum circuit implementing a transverse-field Ising model\"\"\"\n",
        "    qc = QuantumCircuit(num_qubits)\n",
        "    if initial_state_angle:\n",
        "        qc.ry(initial_state_angle, range(num_qubits))\n",
        "    qc.rx(rx_angle / 2, range(num_qubits))\n",
        "    for i in range(num_steps):\n",
        "        for j, layer in enumerate(coloring):\n",
        "            edges = coloring[layer]\n",
        "            if layer_noise_models:\n",
        "                qc.append(layer_noise_models[j], qargs=range(num_qubits))\n",
        "            for edge in edges:\n",
        "                qc.rzz(rzz_angle, *edge)\n",
        "        if i == num_steps - 1:\n",
        "            qc.rx(rx_angle / 2, range(num_qubits))\n",
        "        else:\n",
        "            qc.rx(rx_angle, range(num_qubits))\n",
        "    return qc\n",
        "\n",
        "\n",
        "# Create the noiseless and noisy circuits\n",
        "noiseless_circs = []\n",
        "noisy_circs = []\n",
        "for steps in range(1, num_steps + 1):\n",
        "    noiseless_circs.append(\n",
        "        generate_ising_circuit(\n",
        "            num_qubits,\n",
        "            steps,\n",
        "            rx_angle,\n",
        "            rzz_angle,\n",
        "            color_to_edge,\n",
        "            initial_state_angle=initial_state_angle,\n",
        "        )\n",
        "    )\n",
        "    noisy_circs_per_step = []\n",
        "    for noise_models in noise_models_per_EPLG:\n",
        "        noisy_circs_per_step.append(\n",
        "            generate_ising_circuit(\n",
        "                num_qubits,\n",
        "                steps,\n",
        "                rx_angle,\n",
        "                rzz_angle,\n",
        "                color_to_edge,\n",
        "                layer_noise_models=noise_models,\n",
        "                initial_state_angle=initial_state_angle,\n",
        "            )\n",
        "        )\n",
        "    noisy_circs.append(noisy_circs_per_step)\n",
        "print(\n",
        "    f\"{num_steps} noiseless and {num_steps * len(target_EPLGs)} noisy Trotter circuits generated. {num_steps} different depths across {len(target_EPLGs)} different noise models\"\n",
        ")\n",
        "print(\"\\nBelow: Initial state and one noisy Trotter step.\")\n",
        "noisy_circs[0][0].draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "007a0560-e1bf-4715-a2fd-a8a5f1e5ac24",
      "metadata": {},
      "source": [
        "<span id=\"specify-observable-and-run-simulations\" />\n",
        "\n",
        "## オブザーバブルを指定してシミュレーションを実行する\n",
        "\n",
        "このデモでは、平均2サイト相関関数の期待値をシミュレーションします：\n",
        "\n",
        "$\\langle O \\rangle = \\langle Z_{tot}^2(s) \\rangle = \\frac{1}{N^2}\\sum \\langle \\Psi(\\theta)|(\\mathscr{U}^{\\dagger})^sZ_jZ_k(\\mathscr{U})^s|\\Psi(\\theta) \\rangle$\n",
        "\n",
        "ここで、 $\\Psi(\\theta)$ はすべての量子ビットに対する一様な $R_y(\\theta)$ 回転に対応し、 $\\mathscr{U}^s$ は $s$ のトロッター層を表し、 $(j,k)$ は格子上のすべての連結な頂点ペアをインデックス付けする。\n",
        "\n",
        "最後に、各回路について観測可能な期待値をシミュレート `pauli_prop` するために を使用する。 この9キュービットの実証実験では、すべてのシミュレーションを**厳密**に行います。 **パウリ伝播の切り捨ては行われないため、異なるノイズモデル間における期待値の差は、すべてゲート誤差に起因するとみなすことができる**。 シミュレーションのプロセスは、以下の4つのステップで行われます\n",
        "\n",
        "* 回路内のクリフォードゲートを、以下の方法を用いて回路の前方へと展開してください。 `pauli_prop.evolve_through_cliffords`\n",
        "* 回路の非クリフォード部分を通じて、観測可能量を以下を用いて伝播させる `pauli_prop.propagate_through_circuit`\n",
        "  * 観測量が完全なパウリ空間の大きさまで拡大するように設定することで、厳密なシミュレーションを行う。 $4^9$\n",
        "* Qiskitの機能を使用して、進化した観測量を回路のクリフォード部分を通じて伝播させる `SparsePauliOp.evolve`\n",
        "* $O$ における各対角項の係数を合計することで、ゼロ状態に対する期待値 $|0\\rangle^{\\otimes N}$ を推定する（すべての量子ビットにおいて $Z$ または $I$ を含む項）\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "7ceb7937-ef7d-483a-b49a-d3aa2a3e6c43",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Ran 10 noiseless and 50 noisy simulations in 103s.\n"
          ]
        }
      ],
      "source": [
        "import time\n",
        "\n",
        "from pauli_prop import evolve_through_cliffords, propagate_through_circuit\n",
        "from qiskit.quantum_info import Pauli\n",
        "\n",
        "# Average ZZ-correlator observable\n",
        "id_pauli = Pauli(\"I\" * num_qubits)\n",
        "observable = 2 * SparsePauliOp(\n",
        "    [\n",
        "        id_pauli.dot(Pauli(\"ZZ\"), [i, j])\n",
        "        for i in range(num_qubits)\n",
        "        for j in range(i + 1, num_qubits)\n",
        "    ]\n",
        ")\n",
        "observable /= num_qubits**2\n",
        "\n",
        "# Pauli propagation parameters\n",
        "max_terms = 4**num_qubits  # Exact propagation\n",
        "atol = 1e-12\n",
        "\n",
        "# Run simulations\n",
        "exact_evs = []\n",
        "noisy_evs = [[] for _ in range(len(target_EPLGs))]\n",
        "st = time.perf_counter()\n",
        "for i, noiseless_circ in enumerate(noiseless_circs):\n",
        "    cliff, non_cliff = evolve_through_cliffords(noiseless_circ)\n",
        "    evolved_obs = propagate_through_circuit(\n",
        "        observable, non_cliff, max_terms=max_terms, atol=atol, frame=\"h\"\n",
        "    )[0]\n",
        "    evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame=\"h\")\n",
        "    exact_evs.append(\n",
        "        float(evolved_obs.coeffs[~evolved_obs.paulis.x.any(axis=1)].sum())\n",
        "    )\n",
        "    for j in range(len(target_EPLGs)):\n",
        "        noisy_circ = noisy_circs[i][j]\n",
        "        cliff, non_cliff = evolve_through_cliffords(noisy_circ)\n",
        "        evolved_obs = propagate_through_circuit(\n",
        "            observable, non_cliff, max_terms=max_terms, atol=1e-12, frame=\"h\"\n",
        "        )[0]\n",
        "        evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame=\"h\")\n",
        "        noisy_evs[j].append(\n",
        "            float(evolved_obs.coeffs[~evolved_obs.paulis.x.any(axis=1)].sum())\n",
        "        )\n",
        "print(\n",
        "    f\"Ran {len(noiseless_circs)} noiseless and {len(target_EPLGs) * num_steps} noisy simulations in {int(time.perf_counter() - st)}s.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d138fc46-5a47-46d8-9074-6aa30b273a86",
      "metadata": {},
      "source": [
        "<span id=\"observe-effect-of-gate-error-on-the-model\" />\n",
        "\n",
        "## ゲート誤差がモデルに及ぼす影響を観察する\n",
        "\n",
        "これは9キュービットの実験であるため、パウリ伝播ルーチンは厳密であり、ノイズプロットに現れる誤差はすべてゲート誤差に起因するものであることを覚えておいてください。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "73b00aa9-ee66-4b65-ad41-f6278555e5c5",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": [
          "remove-input"
        ]
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/pauli-prop/guides/simulate-noisy-expectation-values/extracted-outputs/73b00aa9-ee66-4b65-ad41-f6278555e5c5-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "xs = range(1, num_steps + 1)\n",
        "plt.plot(xs, exact_evs, label=\"Noiseless\", color=\"black\", marker=\"o\")\n",
        "colors = [\".3\", \".4\", \".5\", \".6\", \".7\"]\n",
        "for i, evs in enumerate(noisy_evs):\n",
        "    plt.plot(\n",
        "        xs,\n",
        "        evs,\n",
        "        label=f\"{target_EPLGs[i]} EPLG\",\n",
        "        linestyle=\"--\",\n",
        "        color=colors[i],\n",
        "        marker=\"o\",\n",
        "    )\n",
        "plt.xlabel(\"# Trotter steps\")\n",
        "plt.ylabel(r\"$\\langle Z_{tot}^2 \\rangle$\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}