{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Simulation de systèmes quantiques soumis à du bruit à l'aide de la propagation de Pauli\"\n",
        "description: \"Simuler des systèmes quantiques soumis à du bruit à l'aide de la propagation de Pauli pour la dernière version de la propagation de Pauli\"\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",
        "# Simulation de systèmes quantiques soumis à du bruit à l'aide de la propagation de Pauli\n",
        "\n",
        "{/* cspell:ignore mathscr, mapsto */}\n",
        "\n",
        "Dans ce guide, nous utilisons le package `pauli-prop` pour simuler de manière classique la dynamique temporelle d'un modèle d'Ising à champ transversal (TFIM) à neuf qubits avec bruit, sur un réseau carré de 3x3. Nous utilisons les instructions « [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) » pour définir un canal de bruit, $\\Lambda$, agissant sur un ensemble de couches d'intrication, $\\mathcal{U}$. Nous faisons ensuite remonter l'observable, $O$, à travers le circuit bruité et estimons les valeurs attendues pour divers modèles de bruit, ainsi que pour le cas sans bruit.\n",
        "\n",
        "![Véhicule électrique bruyant](https://quantum.cloud.ibm.com/docs/images/addons/pauli-prop/noisy_ev.avif)\n",
        "\n",
        "Au fur et à mesure que l'observable se propage en sens inverse à travers le circuit, chaque canal de bruit, $\\Lambda_k$, associé à une couche d'intrication, $\\mathcal{U}_k$, atténue les termes de Pauli dans $O$ qui anticommutent avec ses générateurs de Pauli-Lindblad. Plus précisément, si $G_{k,i}$ est un générateur de Pauli de $\\Lambda_k$ avec un taux $\\gamma_{k,i}$, alors un terme de Pauli, $P$, dans $O$ se transforme comme suit : $c_P \\mapsto c_P e^{-2\\gamma_{k,i}} \\quad \\text{if } \\{P, G_{k,i}\\}=0$, où $c_P$ est le coefficient de $P$. Une fois que $O$ a été propagé jusqu’au début du circuit, la valeur attendue par rapport à l’état zéro, $|0\\rangle^{\\otimes N}$, peut être calculée de manière triviale en additionnant les coefficients de chaque terme diagonal dans $O$ (termes contenant $Z$ ou $I$ sur tous les qubits).\n",
        "\n",
        "Flux de travail :\n",
        "\n",
        "* Spécifier le réseau TFIM et utiliser la coloration des arêtes pour identifier un ensemble minimal de couches d'intrication\n",
        "* Générer des modèles de bruit synthétique, $\\Lambda_k$, pour chaque couche d'intrication distincte, $U_k$\n",
        "  * Créer des modèles de bruit à différentes échelles afin d'étudier l'impact du bruit de porte sur le système\n",
        "* Créer des circuits quantiques sans bruit et avec bruit pour les différentes profondeurs et échelles de bruit d'intérêt\n",
        "  * Dans les circuits bruyants, des instructions `PauliLindbladError` sont insérées avant chaque couche d'intrication\n",
        "* Utiliser la propagation de Pauli pour simuler les valeurs exactes des espérances du système à différentes profondeurs\n",
        "  * Pour neuf qubits, cela s'effectue en faisant évoluer l' $O$ e vers des termes de type $4^9$, couvrant ainsi l'intégralité de l'espace de Pauli\n",
        "* Utiliser la propagation de Pauli pour simuler des valeurs attendues bruitées\n",
        "* Observez comment l'augmentation du bruit de la porte dégrade la précision du modèle quantique\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",
        "## Générer un réseau carré de 3x3 s et trouver une coloration à 4 couleurs sur les arêtes\n",
        "\n",
        "Les sommets du graphe représentent des qubits, et les arêtes représentent une connexion entre deux qubits. La coloration des arêtes correspond à des couches d'intrication distinctes dans le circuit quantique, de sorte que les portes situées sur des connexions associées à des couleurs différentes ne peuvent pas être appliquées simultanément.\n",
        "\n",
        "Il est souvent important d'identifier un ensemble minimal de couches d'intrication uniques pour mettre en œuvre des protocoles d'apprentissage du bruit efficaces, car le bruit de chaque couche doit être appris indépendamment. Plus il y a de couches à apprendre, plus il faut effectuer de prises de vue à partir du QPU. Pour cette démonstration, nous utilisons les informations relatives aux couches pour construire des circuits bruités et injecter des instructions « [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) » avant `qiskit-aer` chaque couche d’intrication afin de modéliser le bruit des portes du 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",
        "## Générer des modèles de bruit synthétique\n",
        "\n",
        "Avant de créer les circuits quantiques, nous générons un modèle de bruit (instance de [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) ) pour chacune des couches d'intrication. Nous les intégrerons ultérieurement dans nos circuits quantiques sous forme d'instructions. Pour chaque couche, nous générons des canaux de bruit à différentes échelles. Plus précisément, nous générons des modèles de bruit avec [un indicateur d’erreur par porte en couches (EPLG)](https://www.ibm.com/quantum/blog/quantum-metric-layer-fidelity) d’environ et `.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",
        "## Créer les circuits quantiques\n",
        "\n",
        "Dans cette démonstration, nous simulons la dynamique temporelle d'un modèle d'Ising à champ transversal (TFIM) pour un nombre croissant d'étapes de Trotter (de 1 à 10 étapes). Pour chacune des 10 profondeurs de circuit, nous simulons l'effet du bruit de grille, à partir de modèles de bruit à différentes échelles (`EPLGs = .0004, .0008, .0012, .0016, .002`). Le bruit est injecté dans le circuit `QuantumCircuit` sous la forme d'une instruction « [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) » issue de Qiskit Aer. L'hamiltonien considéré est le suivant :\n",
        "\n",
        "$H = -J\\sum\\limits_{\\langle i,j \\rangle} Z_iZ_j + h\\sum\\limits_iX_i$\n",
        "\n",
        "où $J>0$ décrit le couplage entre les spins voisins les plus proches, $i<j$, et $h$ représente le champ transversal global.\n",
        "\n",
        "Nous mettons ici en œuvre l'hamiltonien évolué dans le temps à différentes échelles de temps et de bruit. Nous créons au total 60 circuits : 10 circuits silencieux dont la profondeur de Trotter varie, et 50 circuits bruyants correspondant aux 10 profondeurs de Trotter, répartis sur cinq échelles de bruit. Pour un graphe de connectivité donné, le modèle est paramétré par quelques variables :\n",
        "\n",
        "* `num_steps`: Le nombre de pas de Trotter\n",
        "* `J`: Force de couplage des sites connectés\n",
        "* `h`: Intensité du champ magnétique externe\n",
        "* `dt`: Variation du temps sur une pas de Trotter\n",
        "* `initial_state_angle`: Une excitation initiale, $R_y(\\theta)$, à appliquer de manière uniforme à tous les qubits\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",
        "## Spécifier les observables et lancer les simulations\n",
        "\n",
        "Dans cette démonstration, nous simulons les valeurs attendues du corrélateur moyen à deux sites :\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",
        "où $\\Psi(\\theta)$ correspond à une rotation uniforme de type « $R_y(\\theta)$ » appliquée à tous les qubits, $\\mathscr{U}^s$ décrit $s$ couches de Trotter, et $(j,k)$ répertorie toutes les paires de sommets connectés sur le réseau.\n",
        "\n",
        "Enfin, nous utilisons `pauli_prop` pour simuler les valeurs attendues observables pour chacun des circuits. Pour cette démonstration à neuf qubits, nous effectuons toutes les simulations **de manière exacte**. **Aucune troncature de propagation de Pauli ne sera effectuée; par conséquent, les différences entre les valeurs attendues selon les différents modèles de bruit peuvent être entièrement attribuées à l'erreur de la porte**. Le processus de simulation se déroule en quatre étapes :\n",
        "\n",
        "* Déplacez les portes de Clifford du circuit vers l'avant de celui-ci à l'aide de `pauli_prop.evolve_through_cliffords`\n",
        "* Propager l'observable à travers la partie non-Clifford du circuit à l'aide de `pauli_prop.propagate_through_circuit`\n",
        "  * Nous réalisons des simulations exactes en laissant l'observable s'étendre jusqu'à couvrir l'intégralité de l'espace de Pauli, $4^9$\n",
        "* Propager l'observable évoluée à travers la partie Clifford du circuit à l'aide de Qiskit’s `SparsePauliOp.evolve`\n",
        "* Estimer la valeur attendue par rapport à l'état zéro, $|0\\rangle^{\\otimes N}$, en additionnant les coefficients de chaque terme diagonal de $O$ (termes contenant $Z$ ou $I$ sur tous les qubits)\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",
        "## Observer l'effet de l'erreur de grille sur le modèle\n",
        "\n",
        "N'oubliez pas que, puisqu'il s'agit d'une expérience à neuf qubits, la routine de propagation de Pauli est exacte, et que toutes les erreurs observées dans les graphiques de bruit peuvent être attribuées à des erreurs de porte.\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
}