{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Simulación de sistemas cuánticos con ruido mediante la propagación de Pauli\"\n",
        "description: \"Simula sistemas cuánticos con ruido mediante la propagación de Pauli en la última versión de la propagación 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",
        "# Simulación de sistemas cuánticos con ruido mediante la propagación de Pauli\n",
        "\n",
        "{/* cspell:ignore mathscr, mapsto */}\n",
        "\n",
        "En esta guía utilizamos el paquete `pauli-prop` para simular de forma clásica la dinámica temporal de un modelo de Ising de campo transversal (TFIM) de nueve qubits con ruido en una red cuadrada de 3x3. Utilizamos las instrucciones de « [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) » para definir un canal de ruido, $\\Lambda$, que actúa sobre un conjunto de capas de entrelazamiento, $\\mathcal{U}$. A continuación, propagamos el observable, $O$, hacia atrás a través del circuito con ruido y estimamos los valores esperados para diversos modelos de ruido, así como para el caso sin ruido.\n",
        "\n",
        "![Vehículo eléctrico ruidoso](https://eu-de.quantum.cloud.ibm.com/docs/images/addons/pauli-prop/noisy_ev.avif)\n",
        "\n",
        "A medida que la variable observable se propaga hacia atrás a través del circuito, cada canal de ruido, $\\Lambda_k$, asociado a la capa de entrelazamiento, $\\mathcal{U}_k$, amortigua los términos de Pauli en $O$ que anticomutan con sus generadores de Pauli-Lindblad. Concretamente, si $G_{k,i}$ es un generador de Pauli de $\\Lambda_k$ con tasa $\\gamma_{k,i}$, entonces un término de Pauli, $P$, en $O$ se transforma como: $c_P \\mapsto c_P e^{-2\\gamma_{k,i}} \\quad \\text{if } \\{P, G_{k,i}\\}=0$, donde $c_P$ es el coeficiente de $P$. Una vez que $O$ se ha propagado hasta el principio del circuito, el valor esperado con respecto al estado cero, $|0\\rangle^{\\otimes N}$, puede calcularse de forma trivial sumando los coeficientes de cada término diagonal en $O$ (términos que contienen $Z$ o $I$ en todos los qubits).\n",
        "\n",
        "Flujo de trabajo:\n",
        "\n",
        "* Especifica la red TFIM y utiliza la coloración de aristas para identificar un conjunto mínimo de capas entrelazadas\n",
        "* Generar modelos de ruido sintético, $\\Lambda_k$, para cada capa de entrelazamiento única, $U_k$\n",
        "  * Crear modelos de ruido a distintas escalas para estudiar el impacto del ruido de las compuertas en el sistema\n",
        "* Crear circuitos cuánticos sin ruido y con ruido para las distintas profundidades y escalas de ruido de interés\n",
        "  * En los circuitos con ruido, se insertan instrucciones `PauliLindbladError` antes de cada capa de entrelazamiento\n",
        "* Utiliza la propagación de Pauli para simular los valores exactos de la esperanza del sistema a distintas profundidades\n",
        "  * En el caso de nueve qubits, esto se consigue dejando que « $O$ » crezca hasta incluir términos de « $4^9$ », cubriendo así todo el espacio de Pauli\n",
        "* Utiliza la propagación de Pauli para simular valores esperados con ruido\n",
        "* Observa cómo el aumento del ruido de la puerta degrada la precisión del modelo cuántico\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",
        "## Genera una red cuadrada de 3x3 y busca una coloración de 4 colores en las aristas\n",
        "\n",
        "Los vértices del grafo representan qubits, y las aristas representan una conexión entre dos qubits. El color de los bordes corresponde a capas de entrelazamiento únicas en el circuito cuántico, de modo que las puertas situadas en conexiones asociadas a colores diferentes no pueden aplicarse simultáneamente.\n",
        "\n",
        "A menudo resulta importante identificar un conjunto mínimo de capas de entrelazamiento únicas para implementar protocolos eficientes de aprendizaje del ruido, ya que el ruido de cada capa debe aprenderse de forma independiente. Cuantas más capas tengamos que aprender, más capturas tendremos que realizar desde la QPU. Para esta demostración, utilizamos la información de las capas para construir circuitos con ruido e inyectamos instrucciones « [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) » antes `qiskit-aer` de cada capa de entrelazamiento, con el fin de modelar el ruido de las puertas de la 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",
        "## Generar modelos de ruido sintético\n",
        "\n",
        "Antes de crear los circuitos cuánticos, generamos un modelo de ruido (una instancia de [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) ) para cada una de las capas de entrelazamiento. Más adelante las incorporaremos como instrucciones en nuestros circuitos cuánticos. Para cada capa, generamos canales de ruido de distintas escalas. En concreto, generamos modelos de ruido con [un error por puerta por capa (EPLG)](https://www.ibm.com/quantum/blog/quantum-metric-layer-fidelity) de aproximadamente `.0004, .0008, .0012, .0016,` y `.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",
        "## Crear los circuitos cuánticos\n",
        "\n",
        "En esta demostración, simulamos la dinámica temporal de un modelo de Ising de campo transversal (TFIM) para un número creciente de pasos de Trotter (de 1 a 10 pasos). Para cada una de las 10 profundidades del circuito, simulamos el efecto del ruido de puerta, partiendo de modelos de ruido de distintas escalas (`EPLGs = .0004, .0008, .0012, .0016, .002`). El ruido se inserta en el circuito `QuantumCircuit` mediante una instrucción « [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) » de Qiskit Aer. El hamiltoniano que se considera es:\n",
        "\n",
        "$H = -J\\sum\\limits_{\\langle i,j \\rangle} Z_iZ_j + h\\sum\\limits_iX_i$\n",
        "\n",
        "donde $J>0$ describe el acoplamiento entre los espines más próximos, $i<j$, y $h$ es el campo transversal global.\n",
        "\n",
        "Aquí aplicamos el hamiltoniano evolucionado en el tiempo a diversas escalas de tiempo y de ruido. Creamos un total de 60 circuitos: 10 circuitos sin ruido que varían en cuanto a la profundidad de Trotter, y 50 circuitos con ruido para las 10 profundidades de Trotter, distribuidos en cinco escalas de ruido. Dado un grafo de conectividad, el modelo se parametriza mediante unas pocas variables:\n",
        "\n",
        "* `num_steps`: El número de pasos de Trotter\n",
        "* `J`: Fuerza de acoplamiento de los sitios conectados\n",
        "* `h`: Intensidad del campo magnético externo\n",
        "* `dt`: Variación del tiempo a lo largo de un paso de Trotter\n",
        "* `initial_state_angle`: Una excitación inicial, $R_y(\\theta)$, que se aplicará de manera uniforme a todos los 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",
        "## Especificar la variable observable y ejecutar simulaciones\n",
        "\n",
        "Para esta demostración, simulamos los valores esperados del correlador medio de dos sitios:\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",
        "donde $\\Psi(\\theta)$ corresponde a una rotación uniforme $R_y(\\theta)$ de todos los qubits, $\\mathscr{U}^s$ describe $s$ capas de Trotter, y $(j,k)$ indexa todos los pares de vértices conectados en la red.\n",
        "\n",
        "Por último, utilizamos `pauli_prop` para simular los valores esperados observables para cada uno de los circuitos. Para esta demostración de nueve qubits, realizamos todas las simulaciones **de forma exacta**. **No se llevará a cabo ningún truncamiento de la propagación de Pauli; por lo tanto, las diferencias en los valores esperados entre los distintos modelos de ruido pueden atribuirse íntegramente al error de la puerta**. El proceso de simulación se lleva a cabo en cuatro pasos:\n",
        "\n",
        "* Mueve las puertas de Clifford del circuito hacia la parte delantera del mismo utilizando `pauli_prop.evolve_through_cliffords`\n",
        "* Propaga la observable a través de la parte no-Clifford del circuito utilizando `pauli_prop.propagate_through_circuit`\n",
        "  * Realizamos simulaciones exactas permitiendo que el observable alcance el tamaño del espacio completo de Pauli, $4^9$\n",
        "* Propaga el observable evolucionado a través de la parte de Clifford del circuito utilizando Qiskit's `SparsePauliOp.evolve`\n",
        "* Calcula el valor esperado con respecto al estado cero, $|0\\rangle^{\\otimes N}$, sumando los coeficientes de cada término diagonal de $O$ (términos que contienen $Z$ o $I$ en todos los 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",
        "## Observa el efecto del error de puerta en el modelo\n",
        "\n",
        "Recuerda que, dado que se trata de un experimento con nueve qubits, la rutina de propagación de Pauli es exacta, y todo el error que se observa en los gráficos de ruido puede atribuirse al error de las puertas.\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
}